Skip to content

Commit

Permalink
Copied Apple's semantics for switches, made checkboxes the same. (#16211
Browse files Browse the repository at this point in the history
)
  • Loading branch information
gaaclarke committed Feb 5, 2020
1 parent 4aa2083 commit 036c370
Showing 1 changed file with 141 additions and 39 deletions.
180 changes: 141 additions & 39 deletions shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm
Expand Up @@ -45,6 +45,75 @@

} // namespace

/// A proxy class for SemanticsObject and UISwitch. For most Accessibility and
/// SemanticsObject methods it delegates to the semantics object, otherwise it
/// sends messages to the UISwitch.
@interface FlutterSwitchSemanticsObject : UISwitch
@end

@implementation FlutterSwitchSemanticsObject {
SemanticsObject* _semanticsObject;
}

- (instancetype)initWithSemanticsObject:(SemanticsObject*)semanticsObject {
self = [super init];
if (self) {
_semanticsObject = [semanticsObject retain];
}
return self;
}

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

- (NSMethodSignature*)methodSignatureForSelector:(SEL)sel {
NSMethodSignature* result = [super methodSignatureForSelector:sel];
if (!result) {
result = [_semanticsObject methodSignatureForSelector:sel];
}
return result;
}

- (void)forwardInvocation:(NSInvocation*)anInvocation {
[anInvocation setTarget:_semanticsObject];
[anInvocation invoke];
}

- (CGRect)accessibilityFrame {
return [_semanticsObject accessibilityFrame];
}

- (id)accessibilityContainer {
return [_semanticsObject accessibilityContainer];
}

- (NSString*)accessibilityLabel {
return [_semanticsObject accessibilityLabel];
}

- (NSString*)accessibilityHint {
return [_semanticsObject accessibilityHint];
}

- (NSString*)accessibilityValue {
if ([_semanticsObject node].HasFlag(flutter::SemanticsFlags::kIsToggled) ||
[_semanticsObject node].HasFlag(flutter::SemanticsFlags::kIsChecked)) {
self.on = YES;
} else {
self.on = NO;
}

if (![_semanticsObject isAccessibilityBridgeAlive]) {
return nil;
} else {
return [super accessibilityValue];
}
}

@end // FlutterSwitchSemanticsObject

@implementation FlutterCustomAccessibilityAction {
}
@end
Expand Down Expand Up @@ -265,9 +334,23 @@ - (NSString*)accessibilityHint {
- (NSString*)accessibilityValue {
if (![self isAccessibilityBridgeAlive])
return nil;
if ([self node].value.empty())
return nil;
return @([self node].value.data());

if (![self node].value.empty()) {
return @([self node].value.data());
}

// FlutterSwitchSemanticsObject should supercede these conditionals.
if ([self node].HasFlag(flutter::SemanticsFlags::kHasToggledState) ||
[self node].HasFlag(flutter::SemanticsFlags::kHasCheckedState)) {
if ([self node].HasFlag(flutter::SemanticsFlags::kIsToggled) ||
[self node].HasFlag(flutter::SemanticsFlags::kIsChecked)) {
return @"1";
} else {
return @"0";
}
}

return nil;
}

- (CGRect)accessibilityFrame {
Expand Down Expand Up @@ -423,10 +506,12 @@ - (UIAccessibilityTraits)accessibilityTraits {
[self node].HasAction(flutter::SemanticsAction::kDecrease)) {
traits |= UIAccessibilityTraitAdjustable;
}
// TODO(jonahwilliams): switches should have a value of "on" or "off"
if ([self node].HasFlag(flutter::SemanticsFlags::kIsSelected) ||
[self node].HasFlag(flutter::SemanticsFlags::kIsToggled) ||
[self node].HasFlag(flutter::SemanticsFlags::kIsChecked)) {
// FlutterSwitchSemanticsObject should supercede these conditionals.
if ([self node].HasFlag(flutter::SemanticsFlags::kHasToggledState) ||
[self node].HasFlag(flutter::SemanticsFlags::kHasCheckedState)) {
traits |= UIAccessibilityTraitButton;
}
if ([self node].HasFlag(flutter::SemanticsFlags::kIsSelected)) {
traits |= UIAccessibilityTraitSelected;
}
if ([self node].HasFlag(flutter::SemanticsFlags::kIsButton)) {
Expand Down Expand Up @@ -755,49 +840,66 @@ - (BOOL)accessibilityScroll:(UIAccessibilityScrollDirection)direction {
platform_view_->DispatchSemanticsAction(uid, action, std::move(args));
}

static void ReplaceSemanticsObject(SemanticsObject* oldObject,
SemanticsObject* newObject,
NSMutableDictionary<NSNumber*, SemanticsObject*>* objects) {
// `newObject` should represent the same id as `oldObject`.
assert(oldObject.node.id == newObject.node.id);
NSNumber* nodeId = @(oldObject.node.id);
NSUInteger positionInChildlist = [oldObject.parent.children indexOfObject:oldObject];
SemanticsObject* parent = oldObject.parent;
[objects removeObjectForKey:nodeId];
newObject.parent = parent;
[newObject.parent.children replaceObjectAtIndex:positionInChildlist withObject:newObject];
objects[nodeId] = newObject;
}

static SemanticsObject* CreateObject(const flutter::SemanticsNode& node,
fml::WeakPtr<AccessibilityBridge> weak_ptr) {
if (node.HasFlag(flutter::SemanticsFlags::kIsTextField) &&
!node.HasFlag(flutter::SemanticsFlags::kIsReadOnly)) {
// Text fields are backed by objects that implement UITextInput.
return [[[TextInputSemanticsObject alloc] initWithBridge:weak_ptr uid:node.id] autorelease];
} else if (node.HasFlag(flutter::SemanticsFlags::kHasToggledState) ||
node.HasFlag(flutter::SemanticsFlags::kHasCheckedState)) {
SemanticsObject* delegateObject = [[FlutterSemanticsObject alloc] initWithBridge:weak_ptr
uid:node.id];
return (SemanticsObject*)[[[FlutterSwitchSemanticsObject alloc]
initWithSemanticsObject:delegateObject] autorelease];
[delegateObject release];
} else {
return [[[FlutterSemanticsObject alloc] initWithBridge:weak_ptr uid:node.id] autorelease];
}
}

static bool DidFlagChange(const flutter::SemanticsNode& oldNode,
const flutter::SemanticsNode& newNode,
SemanticsFlags flag) {
return oldNode.HasFlag(flag) != newNode.HasFlag(flag);
}

SemanticsObject* AccessibilityBridge::GetOrCreateObject(int32_t uid,
flutter::SemanticsNodeUpdates& updates) {
SemanticsObject* object = objects_.get()[@(uid)];
if (!object) {
// New node case: simply create a new SemanticsObject.
flutter::SemanticsNode node = updates[uid];
if (node.HasFlag(flutter::SemanticsFlags::kIsTextField) &&
!node.HasFlag(flutter::SemanticsFlags::kIsReadOnly)) {
// Text fields are backed by objects that implement UITextInput.
object = [[[TextInputSemanticsObject alloc] initWithBridge:GetWeakPtr() uid:uid] autorelease];
} else {
object = [[[FlutterSemanticsObject alloc] initWithBridge:GetWeakPtr() uid:uid] autorelease];
}

object = CreateObject(updates[uid], GetWeakPtr());
objects_.get()[@(uid)] = object;
} else {
// Existing node case
auto nodeEntry = updates.find(object.node.id);
if (nodeEntry != updates.end()) {
// There's an update for this node
flutter::SemanticsNode node = nodeEntry->second;
BOOL isTextField = node.HasFlag(flutter::SemanticsFlags::kIsTextField);
BOOL wasTextField = object.node.HasFlag(flutter::SemanticsFlags::kIsTextField);
BOOL isReadOnly = node.HasFlag(flutter::SemanticsFlags::kIsReadOnly);
BOOL wasReadOnly = object.node.HasFlag(flutter::SemanticsFlags::kIsReadOnly);
if (wasTextField != isTextField || isReadOnly != wasReadOnly) {
// The node changed its type from text field to something else, or vice versa. In this
// case, we cannot reuse the existing SemanticsObject implementation. Instead, we replace
// it with a new instance.
NSUInteger positionInChildlist = [object.parent.children indexOfObject:object];
SemanticsObject* parent = object.parent;
[objects_ removeObjectForKey:@(node.id)];
if (isTextField && !isReadOnly) {
// Text fields are backed by objects that implement UITextInput.
object = [[[TextInputSemanticsObject alloc] initWithBridge:GetWeakPtr()
uid:uid] autorelease];
} else {
object = [[[FlutterSemanticsObject alloc] initWithBridge:GetWeakPtr()
uid:uid] autorelease];
}
object.parent = parent;
[object.parent.children replaceObjectAtIndex:positionInChildlist withObject:object];
objects_.get()[@(node.id)] = object;
if (DidFlagChange(object.node, node, flutter::SemanticsFlags::kIsTextField) ||
DidFlagChange(object.node, node, flutter::SemanticsFlags::kIsReadOnly) ||
DidFlagChange(object.node, node, flutter::SemanticsFlags::kHasCheckedState) ||
DidFlagChange(object.node, node, flutter::SemanticsFlags::kHasToggledState)) {
// The node changed its type. In this case, we cannot reuse the existing
// SemanticsObject implementation. Instead, we replace it with a new
// instance.
SemanticsObject* newSemanticsObject = CreateObject(node, GetWeakPtr());
ReplaceSemanticsObject(object, newSemanticsObject, objects_.get());
object = newSemanticsObject;
}
}
}
Expand Down

0 comments on commit 036c370

Please sign in to comment.