Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

iOS a11y text entry (~70% of it) #4575

Merged
merged 3 commits into from
Feb 5, 2018
Merged

Conversation

yjbanov
Copy link
Contributor

@yjbanov yjbanov commented Jan 19, 2018

This fixes many points from flutter/flutter#12786.

What works:

  • When "tabbed" or "dragged" into
    • Text fields are announced as "Text field"
    • The hint "double tap to edit"
    • Content of text field is announced
    • It is announced weather a text field is in edit mode
    • The insert mode (e.g. character mode) is not announced.
  • Entered characters are echoed back
  • After inserting space the last word is echoed back
  • Swiping up or down moves the courser
  • Selecting text with reverse-pinch

What doesn't work:

  • No announcement when a text field gains focused via the "tap" gesture
  • The position of the cursor is not announced
  • Copy&paste does not work when rotor is in edit mode

/cc @cbracken who implemented our iOS text entry system

@yjbanov yjbanov requested a review from goderbauer January 19, 2018 22:36
@yjbanov yjbanov changed the title iOS a11y text entry iOS a11y text entry (~70% of it) Jan 20, 2018
Copy link
Member

@chinmaygarde chinmaygarde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the Objective-C objects need to be prefixed with some sort of a flutter specific identifier.

@@ -20,7 +20,7 @@ AccessibilityBridge::AccessibilityBridge(app::ApplicationContext* context)
: writer_(context->ConnectToEnvironmentService<maxwell::ContextWriter>()) {}

void AccessibilityBridge::UpdateSemantics(
const std::vector<blink::SemanticsNode>& update) {
const std::unordered_map<int32_t, blink::SemanticsNode>& update) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This cant possibly compile. The argument is being iterated over and the pair wont contain the id property.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe typedef this to something and document the key.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Which target uses this code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it's the "content_handler". Can I build and test it on a Mac? I can't find anything about it in https://github.com/flutter/engine/blob/master/CONTRIBUTING.md.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking that the default Fuchsia target builds should be fine. https://fuchsia.googlesource.com/docs/+/HEAD/getting_started.md

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This look good now, right?

@@ -23,18 +23,19 @@ class SemanticsUpdate : public fxl::RefCountedThreadSafe<SemanticsUpdate>,

public:
~SemanticsUpdate() override;
static fxl::RefPtr<SemanticsUpdate> create(std::vector<SemanticsNode> nodes);
static fxl::RefPtr<SemanticsUpdate> create(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Static method names start with an uppercase letter. So Create(...)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not adding this method, just changing its signature. I'll happily do the clean-up, but I'd rather do that in a separate PR.


std::vector<SemanticsNode> nodes_;
std::unordered_map<int32_t, SemanticsNode> nodes_;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please document what the new key means.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created type alias SemanticsNodeUpdates and documented it.

@@ -8,11 +8,21 @@
#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterChannels.h"
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h"

#import <UIKit/UIKit.h>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C headers go above the non-system C++ headers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -559,6 +559,10 @@ - (void)dealloc {
[super dealloc];
}

- (FlutterTextInputView*)textInputView {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please ensure that the signatures match. UIKit docs say that this needs to be UIView*. So lets keep that the same in both the header and implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h"
#include "lib/fxl/macros.h"
#include "third_party/skia/include/core/SkMatrix44.h"
#include "third_party/skia/include/core/SkRect.h"

#import <UIKit/UIKit.h>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This goes above the non-system C++ headers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

* Direct children of this semantics object. Each child's `parent` property must
* be equal to this object.
*/
@property(nonatomic, readonly) std::vector<SemanticsObject*>* children;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make these readonly properties const. For example, I doubt it is safe for external accessor of this object to mutate the vector of children.

So this would be something like:
@property(nonatomic, readonly) const std::vector<SemanticsObject*>& children;. If the vector itself can be nullptr, then you may use a pointer instead of a reference.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually SemanticsObject uses a retained mode API. It is mutated in-place as the semantics tree is updated.

*/
@property(nonatomic, readonly) std::vector<SemanticsObject*>* children;

- (BOOL)willCauseLayoutChange:(const blink::SemanticsNode*)node;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method name is not idiomatic Objective-C. -(BOOL)nodeWillCauseLayoutChange:... works better.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

*
* This class is used by `TextInputSemanticsObject`.
*/
@interface InactiveTextInput : UIView<UITextInput>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are no namespaces in Objective-C. So Flutter classes must have some sort of a prefix. So prepend FLT or Flutter to the Objective-C method name. @cbracken probably better knows the convention used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefixed with Flutter, although I see we have a mix of names, some prefixed, others not. I'm guessing this namespace is not exported to developers.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry - missed this patch earlier. Thanks for updating -- Flutter is the prefix we use for all the iOS side work.

*
* This class is used by `TextInputSemanticsObject`.
*/
@interface InactiveTextInput : UIView<UITextInput>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the implementation of this class and it is not in a public header. Did you forget to push some changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Look for changes in accessibility_bridge.mm (GitHub collapsed it)

Copy link
Member

@chinmaygarde chinmaygarde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing out the collapsed file. I added more comments around the leaking ivars that are synthesized implicitly. Please also add the concrete implementations to a separate file.

return YES;
}

@end

@interface InactiveTextPosition : UITextPosition
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please put these in a separate file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


@end

@implementation InactiveTextPosition
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please explicitly synthesize all properties. Though the compiler will synthesize these properties for you, specifying the same makes the intent clearer and makes it obvious that the properties should be collected in dealloc in case ARC is not being used.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not use ARC, so all the properties implicitly synthesized and set by callers during the lifetime of the object will be leaked when this object is collected.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll attempt to remove these classes (see @goderbauer's suggestion to share these with the FlutterTextInputPlugin).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done (removed these classes)

@property(nonatomic, readonly) shell::AccessibilityBridge* bridge;

/**
* The accessibility node used to produce this semantics object.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The semantic nodes... ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@end

/**
* An implementation of `UITextInput` used for text fields that are not currently focused.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we specify the type of focus here (e.g. input focus here as opposed to accessibility focus)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

/**
* An implementation of `SemanticsObject` specialized for expressing text fields.
*
* Uses `InactiveTextInput` and `FlutterTextInputView` to express blurred and focused
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same. We should be specific about the focus type (input vs. accessibility focus) here to avoid future confusion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -108,8 +106,6 @@ - (instancetype)init {
return nil;
}

#pragma mark - Designated initializers
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this no longer true?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A glitch in the matrix. Reverted.

return YES;
}

@end

@interface InactiveTextPosition : UITextPosition
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can we not reuse the TextPosition and TextRange from the active text input?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there every any danger that iOS asks us for a TextPosition or TextRange while the text field is unfocused (returning an InactiveTextPosition/Range) and then when the text field becomes focused iOS passes those InactiveTextPositions/Ranges back to the real text input view, which doesn't know what to do with them and potentially crashes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

return [self.text substringWithRange:textRange];
}

- (void)replaceRange:(UITextRange*)range withText:(NSString*)text {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add comments to the empty and placeholder implementations to indicate if they have been left blank intentionally or if filling them in is a TODO?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

if ([self node].HasFlag(blink::SemanticsFlags::kIsFocused)) {
// The text input view must have a non-trivial size for the accessibility
// system to send text editing events.
[self bridge]->textInputView().frame = CGRectMake(0.0, 0.0, 1.0, 1.0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to confirm (and maybe to add to the comment): The textInputView itself is not focusable directly by the user, right? It remains hidden even though it has a size now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. See the FlutterTextInputViewAccessibilityHider class in 07501fc.

}

- (UIAccessibilityTraits)accessibilityTraits {
// Adding UIAccessibilityTraitKeyboardKey to the trait list so that iOS treat it like
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

treat -> treats?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

return [self textInputSurrogate].accessibilityTraits | UIAccessibilityTraitKeyboardKey;
}

- (CGPoint)accessibilityActivationPoint {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to move the activation point to the center? Isn't that the default?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


#pragma mark - UIAccessibility overrides

- (UIAccessibilityTraits)accessibilityTraits {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this implementation, we currently don't support text fields that at the same time want to be "checked" or "selected" or express any other accessibility trait other than being a text field.

If that has to be that way, we should at least document that the SemanticsFlag.isTextField on iOS is mutually exclusive with many other flags.

Or, could we just or in the other accessibility traits in the TextInputSemanticsObject?

@goderbauer
Copy link
Member

In the PR's description under "What works" you list

The insert mode (e.g. character mode) is not announced.

Should this be moved to "What doesn't work" or should the "not" be removed from the bullet point?

@goderbauer
Copy link
Member

Also: Since the interactions here are fairly complicated, it would be sooo nice to have tests (especially to catch regressions). But setting up a test harness for this is probably outside of the scope of this PR... flutter/flutter#12287

Copy link
Contributor Author

@yjbanov yjbanov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO:

  • fix content_handler
  • move new classes into separate files
  • share TextRange/TextPosition impls
  • make sure FlutterTextInputView is not focusable

@@ -23,18 +23,19 @@ class SemanticsUpdate : public fxl::RefCountedThreadSafe<SemanticsUpdate>,

public:
~SemanticsUpdate() override;
static fxl::RefPtr<SemanticsUpdate> create(std::vector<SemanticsNode> nodes);
static fxl::RefPtr<SemanticsUpdate> create(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not adding this method, just changing its signature. I'll happily do the clean-up, but I'd rather do that in a separate PR.


std::vector<SemanticsNode> nodes_;
std::unordered_map<int32_t, SemanticsNode> nodes_;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created type alias SemanticsNodeUpdates and documented it.

@@ -8,11 +8,21 @@
#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterChannels.h"
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h"

#import <UIKit/UIKit.h>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -559,6 +559,10 @@ - (void)dealloc {
[super dealloc];
}

- (FlutterTextInputView*)textInputView {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h"
#include "lib/fxl/macros.h"
#include "third_party/skia/include/core/SkMatrix44.h"
#include "third_party/skia/include/core/SkRect.h"

#import <UIKit/UIKit.h>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -108,8 +106,6 @@ - (instancetype)init {
return nil;
}

#pragma mark - Designated initializers
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A glitch in the matrix. Reverted.


@end

@implementation InactiveTextPosition
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll attempt to remove these classes (see @goderbauer's suggestion to share these with the FlutterTextInputPlugin).

return [self.text substringWithRange:textRange];
}

- (void)replaceRange:(UITextRange*)range withText:(NSString*)text {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}

- (UIAccessibilityTraits)accessibilityTraits {
// Adding UIAccessibilityTraitKeyboardKey to the trait list so that iOS treat it like
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

return [self textInputSurrogate].accessibilityTraits | UIAccessibilityTraitKeyboardKey;
}

- (CGPoint)accessibilityActivationPoint {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM for the accessibility side of things.

I defer to @cbracken and @chinmaygarde for the text input changes as well as general engine things.


#pragma mark - UIAccessibility overrides

- (void)setSemanticsNode:(const blink::SemanticsNode*)node {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I believe this method and the next two are not really part of UIAccessibility protocol as indicated by the pragma above?

blink::SemanticsNode node = nodeEntry->second;
BOOL isTextField = node.HasFlag(blink::SemanticsFlags::kIsTextField);
BOOL wasTextField = object.node.HasFlag(blink::SemanticsFlags::kIsTextField);
if (wasTextField != isTextField) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe, the way this is set up we will only act correctly on a switch from textfield to non-textfield (and vice versa) if the parent of the switched node is included in the semantics update? If only the switched node is included in the semantics update, there seems to be nothing updating that node's parent's child list to include the new node?

Maybe, instead of holding on to child objects, we need to switch that to just ids? And whenever you need the real object you need to look it up in the objects_ map, which would be the ultimate truth?

Copy link
Contributor Author

@yjbanov yjbanov Jan 31, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. I need to restructure this.

OTOH, why don't we include parents in the update though? Tree depth is O(log(N)) of the size of the tree, so omitting parent nodes doesn't save anything performance-wise. If we had parents with the update, we could update everything in a single depth-first pass. Also, it would remove the need for the objects_ map.

WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ideal case is that we have updates that send a single node only, so sending more nodes would be less good. :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, assuming we don't lose elsewhere. For example, we currently pay for lack of parent information by having to maintain a fml::scoped_nsobject<NSMutableDictionary<NSNumber*, SemanticsObject*>> objects_ map that contains every single node in the tree (i.e. it's O(N) in size).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now, we assume that every node included in the update has changed (and on Android we need to tell the OS about every node that has changed). If we now include unchanged nodes, we'd have to mark those in the update to avoid reporting unchanged nodes as changed...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking of adding a SemanticsNodeUpdate class that has a SemanticsNode containing updated data. nullptr would indicate "there no update to the node itself, only its children".

However, that's coming way too close to @chinmaygarde's proposal for a unified retained-mode semantics tree, so I'm not going to do this as part of this change, and just swap out the child reference using the current system. We can come back to this later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see 4fc35ac

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update, after rebase it's now 5baa927

Copy link
Member

@chinmaygarde chinmaygarde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nits but LGTM otherwise. Thanks!

@@ -65,7 +65,7 @@ class RuntimeHolder : public blink::RuntimeDelegate,
std::string DefaultRouteName() override;
void ScheduleFrame(bool regenerate_layer_tree = true) override;
void Render(std::unique_ptr<flow::LayerTree> layer_tree) override;
void UpdateSemantics(std::vector<blink::SemanticsNode> update) override;
void UpdateSemantics(blink::SemanticsNodeUpdates update) override;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why copy the collection in this call and not pass it by reference instead?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize you did not add this call. So it fine for us to address this in a future patch.

* The keys in the map are stable node IDd, and the values contain
* semantic information for the node corresponding to the ID.
*/
using SemanticsNodeUpdates = std::unordered_map<int32_t, SemanticsNode>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't use javadoc/headerdoc format for docstrings in the engine. Lets follow the CPP guide for consistency go/fluttercpp#Function_Comments.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

_bridge = nullptr;
_children.clear();
[_parent release];
if (_container != nil)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This nil check is unnecessary as calling the release selector on nil is a no-op already.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@yjbanov yjbanov force-pushed the ios-text-entry branch 3 times, most recently from 4fc35ac to 5baa927 Compare January 31, 2018 20:09
Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Can you update flutter/flutter#12786 and/or file new bugs for the things that still don't work so we can track them?

@yjbanov yjbanov merged commit 6dd49f0 into flutter:master Feb 5, 2018
@yjbanov
Copy link
Contributor Author

yjbanov commented Feb 5, 2018

I will update the issues.

@yjbanov yjbanov deleted the ios-text-entry branch June 22, 2021 21:18
auto-submit bot pushed a commit that referenced this pull request Apr 24, 2024
`UIAccessibilityTraitKeyboardKey` was added in #4575 so that `TextInputSemanticsObject` supported VoiceOver gestures for text editing features, such as pinch to select text, and up/down fling to move cursor. After experimenting with it, I found that those features were still available even after removing `UIAccessibilityTraitKeyboardKey`. 

Fixes flutter/flutter#94465.

In Touch Typing Mode:

https://github.com/flutter/engine/assets/15619084/ccfe90ff-d3bc-427b-b1aa-9ec1242c0c89

In Standard Typing Mode:

https://github.com/flutter/engine/assets/15619084/c78b1fb0-0816-41fb-9dd5-ed8b8a4cda2d

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants