Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
qq_34031325
engine
提交
b3a18f51
E
engine
项目概览
qq_34031325
/
engine
与 Fork 源项目一致
从无法访问的项目Fork
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
E
engine
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
未验证
提交
b3a18f51
编写于
2月 09, 2021
作者:
C
chunhtai
提交者:
GitHub
2月 09, 2021
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
unhide uitextinput when focused (#23776)
上级
b9785159
变更
4
隐藏空白更改
内联
并排
Showing
4 changed file
with
190 addition
and
21 deletion
+190
-21
shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h
...form/darwin/ios/framework/Source/FlutterTextInputPlugin.h
+1
-0
shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm
...orm/darwin/ios/framework/Source/FlutterTextInputPlugin.mm
+107
-18
shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m
.../darwin/ios/framework/Source/FlutterTextInputPluginTest.m
+77
-2
shell/platform/darwin/ios/framework/Source/accessibility_text_entry.mm
...m/darwin/ios/framework/Source/accessibility_text_entry.mm
+5
-1
未找到文件。
shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h
浏览文件 @
b3a18f51
...
...
@@ -71,6 +71,7 @@ FLUTTER_DARWIN_EXPORT
@property
(
nonatomic
,
copy
)
UITextContentType
textContentType
API_AVAILABLE
(
ios
(
10
.
0
));
@property
(
nonatomic
,
assign
)
id
<
FlutterTextInputDelegate
>
textInputDelegate
;
@property
(
nonatomic
,
assign
)
UIAccessibilityElement
*
backingTextInputAccessibilityObject
;
@end
#endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERTEXTINPUTPLUGIN_H_
shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm
浏览文件 @
b3a18f51
...
...
@@ -7,10 +7,14 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#include "flutter/fml/logging.h"
#include "flutter/fml/platform/darwin/string_range_sanitization.h"
static
const
char
_kTextAffinityDownstream
[]
=
"TextAffinity.downstream"
;
static
const
char
_kTextAffinityUpstream
[]
=
"TextAffinity.upstream"
;
// A delay before enabling the accessibility of FlutterTextInputView after
// it is activated.
static
constexpr
double
kUITextInputAccessibilityEnablingDelaySeconds
=
0.5
;
// The "canonical" invalid CGRect, similar to CGRectNull, used to
// indicate a CGRect involved in firstRectForRange calculation is
...
...
@@ -424,6 +428,7 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) {
@property
(
nonatomic
,
readonly
)
CATransform3D
editableTransform
;
@property
(
nonatomic
,
assign
)
CGRect
markedRect
;
@property
(
nonatomic
)
BOOL
isVisibleToAutofill
;
@property
(
nonatomic
,
assign
)
BOOL
accessibilityEnabled
;
-
(
void
)
setEditableTransform
:(
NSArray
*
)
matrix
;
@end
...
...
@@ -462,6 +467,7 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) {
_keyboardType
=
UIKeyboardTypeDefault
;
_returnKeyType
=
UIReturnKeyDone
;
_secureTextEntry
=
NO
;
_accessibilityEnabled
=
NO
;
if
(
@available
(
iOS
11.0
,
*
))
{
_smartQuotesType
=
UITextSmartQuotesTypeYes
;
_smartDashesType
=
UITextSmartDashesTypeYes
;
...
...
@@ -1106,16 +1112,52 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) {
[
self
replaceRange
:
_selectedTextRange
withText
:
@""
];
}
-
(
void
)
postAccessibilityNotification
:(
UIAccessibilityNotifications
)
notification
target
:(
id
)
target
{
UIAccessibilityPostNotification
(
notification
,
target
);
}
-
(
void
)
accessibilityElementDidBecomeFocused
{
if
([
self
accessibilityElementIsFocused
])
{
// For most of the cases, this flutter text input view should never
// receive the focus. If we do receive the focus, we make the best effort
// to send the focus back to the real text field.
FML_DCHECK
(
_backingTextInputAccessibilityObject
);
[
self
postAccessibilityNotification
:
UIAccessibilityScreenChangedNotification
target:
_backingTextInputAccessibilityObject
];
}
}
-
(
BOOL
)
accessibilityElementsHidden
{
return
!
_accessibilityEnabled
;
}
@end
/**
* Hides `FlutterTextInputView` from iOS accessibility system so it
* does not show up twice, once where it is in the `UIView` hierarchy,
* and a second time as part of the `SemanticsObject` hierarchy.
*
* This prevents the `FlutterTextInputView` from receiving the focus
* due to swipping gesture.
*
* There are other cases the `FlutterTextInputView` may receive
* focus. One example is during screen changes, the accessibility
* tree will undergo a dramatic structural update. The Voiceover may
* decide to focus the `FlutterTextInputView` that is not involved
* in the structural update instead. If that happens, the
* `FlutterTextInputView` will make a best effort to direct the
* focus back to the `SemanticsObject`.
*/
@interface
FlutterTextInputViewAccessibilityHider
:
UIView
{
}
@end
@implementation
FlutterTextInputViewAccessibilityHider
{
}
-
(
BOOL
)
accessibilityElementsHidden
{
// We are hiding this accessibility element.
// There are 2 accessible elements involved in text entry in 2 different parts of the view
// hierarchy. This `FlutterTextInputView` is injected at the top of key window. We use this as a
// `UITextInput` protocol to bridge text edit events between Flutter and iOS.
//
// We also create ur own custom `UIAccessibilityElements` tree with our `SemanticsObject` to
// mimic the semantics tree from Flutter. We want the text field to be represented as a
// `TextInputSemanticsObject` in that `SemanticsObject` tree rather than in this
// `FlutterTextInputView` bridge which doesn't appear above a text field from the Flutter side.
return
YES
;
}
...
...
@@ -1128,9 +1170,12 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) {
@property
(
nonatomic
,
readonly
)
NSMutableDictionary
<
NSString
*
,
FlutterTextInputView
*>*
autofillContext
;
@property
(
nonatomic
,
assign
)
FlutterTextInputView
*
activeView
;
@property
(
nonatomic
,
strong
)
FlutterTextInputViewAccessibilityHider
*
inputHider
;
@end
@implementation
FlutterTextInputPlugin
@implementation
FlutterTextInputPlugin
{
NSTimer
*
_enableFlutterTextInputViewAccessibilityTimer
;
}
@synthesize
textInputDelegate
=
_textInputDelegate
;
...
...
@@ -1142,6 +1187,7 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) {
_reusableInputView
.
secureTextEntry
=
NO
;
_autofillContext
=
[[
NSMutableDictionary
alloc
]
init
];
_activeView
=
_reusableInputView
;
_inputHider
=
[[
FlutterTextInputViewAccessibilityHider
alloc
]
init
];
}
return
self
;
...
...
@@ -1150,11 +1196,19 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) {
-
(
void
)
dealloc
{
[
self
hideTextInput
];
[
_reusableInputView
release
];
[
_inputHider
release
];
[
_autofillContext
release
];
[
super
dealloc
];
}
-
(
void
)
removeEnableFlutterTextInputViewAccessibilityTimer
{
if
(
_enableFlutterTextInputViewAccessibilityTimer
)
{
[
_enableFlutterTextInputViewAccessibilityTimer
invalidate
];
[
_enableFlutterTextInputViewAccessibilityTimer
release
];
_enableFlutterTextInputViewAccessibilityTimer
=
nil
;
}
}
-
(
UIView
<
UITextInput
>*
)
textInputView
{
return
_activeView
;
}
...
...
@@ -1207,11 +1261,38 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) {
-
(
void
)
showTextInput
{
_activeView
.
textInputDelegate
=
_textInputDelegate
;
[
self
addToInputParentViewIfNeeded
:
_activeView
];
// Adds a delay to prevent the text view from receiving accessibility
// focus in case it is activated during semantics updates.
//
// One common case is when the app navigates to a page with an auto
// focused text field. The text field will activate the FlutterTextInputView
// with a semantics update sent to the engine. The voiceover will focus
// the newly attached active view while performing accessibility update.
// This results in accessibility focus stuck at the FlutterTextInputView.
if
(
!
_enableFlutterTextInputViewAccessibilityTimer
)
{
_enableFlutterTextInputViewAccessibilityTimer
=
[[
NSTimer
scheduledTimerWithTimeInterval
:
kUITextInputAccessibilityEnablingDelaySeconds
target:
self
selector:
@selector
(
enableActiveViewAccessibility
:)
userInfo:
nil
repeats:
NO
]
retain
];
}
[
_activeView
becomeFirstResponder
];
}
-
(
void
)
enableActiveViewAccessibility
:(
NSTimer
*
)
time
{
if
(
_activeView
.
isFirstResponder
)
{
_activeView
.
accessibilityEnabled
=
YES
;
}
[
self
removeEnableFlutterTextInputViewAccessibilityTimer
];
}
-
(
void
)
hideTextInput
{
[
self
removeEnableFlutterTextInputViewAccessibilityTimer
];
_activeView
.
accessibilityEnabled
=
NO
;
[
_activeView
resignFirstResponder
];
[
_activeView
removeFromSuperview
];
[
_inputHider
removeFromSuperview
];
}
-
(
void
)
triggerAutofillSave
:(
BOOL
)
saveEntries
{
...
...
@@ -1356,7 +1437,7 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) {
}
// The UIView to add FlutterTextInputViews to.
-
(
UIView
*
)
textInputParentVie
w
{
-
(
UIView
*
)
keyWindo
w
{
UIWindow
*
keyWindow
=
[
UIApplication
sharedApplication
].
keyWindow
;
NSAssert
(
keyWindow
!=
nullptr
,
@"The application must have a key window since the keyboard client "
...
...
@@ -1364,12 +1445,17 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) {
return
keyWindow
;
}
// The UIView to add FlutterTextInputViews to.
-
(
NSArray
<
UIView
*>*
)
textInputViews
{
return
_inputHider
.
subviews
;
}
// Removes every installed input field, unless it's in the current autofill
// context. May remove the active view too if includeActiveView is YES.
// When clearText is YES, the text on the input fields will be set to empty before
// they are removed from the view hierarchy, to avoid triggering autofill save.
-
(
void
)
cleanUpViewHierarchy
:(
BOOL
)
includeActiveView
clearText
:(
BOOL
)
clearText
{
for
(
UIView
*
view
in
self
.
textInput
ParentView
.
subv
iews
)
{
for
(
UIView
*
view
in
self
.
textInput
V
iews
)
{
if
([
view
isKindOfClass
:[
FlutterTextInputView
class
]]
&&
(
includeActiveView
||
view
!=
_activeView
))
{
FlutterTextInputView
*
inputView
=
(
FlutterTextInputView
*
)
view
;
...
...
@@ -1390,7 +1476,7 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) {
// Changes the visibility of every FlutterTextInputView currently in the
// view hierarchy.
-
(
void
)
changeInputViewsAutofillVisibility
:(
BOOL
)
newVisibility
{
for
(
UIView
*
view
in
self
.
textInput
ParentView
.
subv
iews
)
{
for
(
UIView
*
view
in
self
.
textInput
V
iews
)
{
if
([
view
isKindOfClass
:[
FlutterTextInputView
class
]])
{
FlutterTextInputView
*
inputView
=
(
FlutterTextInputView
*
)
view
;
inputView
.
isVisibleToAutofill
=
newVisibility
;
...
...
@@ -1401,7 +1487,7 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) {
// Resets the client id of every FlutterTextInputView in the view hierarchy
// to 0. Called when a new text input connection will be established.
-
(
void
)
resetAllClientIds
{
for
(
UIView
*
view
in
self
.
textInput
ParentView
.
subv
iews
)
{
for
(
UIView
*
view
in
self
.
textInput
V
iews
)
{
if
([
view
isKindOfClass
:[
FlutterTextInputView
class
]])
{
FlutterTextInputView
*
inputView
=
(
FlutterTextInputView
*
)
view
;
[
inputView
setTextInputClient
:
0
];
...
...
@@ -1410,9 +1496,12 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) {
}
-
(
void
)
addToInputParentViewIfNeeded
:(
FlutterTextInputView
*
)
inputView
{
UIView
*
parentView
=
self
.
textInputParentView
;
if
(
inputView
.
superview
!=
parentView
)
{
[
parentView
addSubview
:
inputView
];
if
(
!
[
inputView
isDescendantOfView
:
_inputHider
])
{
[
_inputHider
addSubview
:
inputView
];
}
UIView
*
parentView
=
self
.
keyWindow
;
if
(
_inputHider
.
superview
!=
parentView
)
{
[
parentView
addSubview
:
_inputHider
];
}
}
...
...
shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m
浏览文件 @
b3a18f51
...
...
@@ -20,6 +20,30 @@ FLUTTER_ASSERT_ARC
-
(
void
)
setMarkedRect
:(
CGRect
)
markedRect
;
-
(
void
)
updateEditingState
;
-
(
BOOL
)
isVisibleToAutofill
;
@end
@interface
FlutterTextInputViewSpy
:
FlutterTextInputView
@property
(
nonatomic
,
assign
)
UIAccessibilityNotifications
receivedNotification
;
@property
(
nonatomic
,
assign
)
id
receivedNotificationTarget
;
@property
(
nonatomic
,
assign
)
BOOL
isAccessibilityFocused
;
-
(
void
)
postAccessibilityNotification
:(
UIAccessibilityNotifications
)
notification
target
:(
id
)
target
;
@end
@implementation
FlutterTextInputViewSpy
{
}
-
(
void
)
postAccessibilityNotification
:(
UIAccessibilityNotifications
)
notification
target
:(
id
)
target
{
self
.
receivedNotification
=
notification
;
self
.
receivedNotificationTarget
=
target
;
}
-
(
BOOL
)
accessibilityElementIsFocused
{
return
_isAccessibilityFocused
;
}
@end
@interface
FlutterSecureTextInputView
:
FlutterTextInputView
...
...
@@ -33,7 +57,7 @@ FLUTTER_ASSERT_ARC
NSMutableDictionary
<
NSString
*
,
FlutterTextInputView
*>*
autofillContext
;
-
(
void
)
collectGarbageInputViews
;
-
(
UIView
*
)
textInputParentView
;
-
(
NSArray
<
UIView
*>*
)
textInputViews
;
@end
@interface
FlutterTextInputPluginTest
:
XCTestCase
...
...
@@ -71,6 +95,22 @@ FLUTTER_ASSERT_ARC
}];
}
-
(
void
)
setTextInputShow
{
FlutterMethodCall
*
setClientCall
=
[
FlutterMethodCall
methodCallWithMethodName
:
@"TextInput.show"
arguments:
@[]];
[
textInputPlugin
handleMethodCall
:
setClientCall
result:
^
(
id
_Nullable
result
){
}];
}
-
(
void
)
setTextInputHide
{
FlutterMethodCall
*
setClientCall
=
[
FlutterMethodCall
methodCallWithMethodName
:
@"TextInput.hide"
arguments:
@[]];
[
textInputPlugin
handleMethodCall
:
setClientCall
result:
^
(
id
_Nullable
result
){
}];
}
-
(
NSMutableDictionary
*
)
mutableTemplateCopy
{
if
(
!
_template
)
{
_template
=
@{
...
...
@@ -88,7 +128,7 @@ FLUTTER_ASSERT_ARC
}
-
(
NSArray
<
FlutterTextInputView
*>*
)
installedInputViews
{
return
[
textInputPlugin
.
textInputParentView
.
subv
iews
return
(
NSArray
<
FlutterTextInputView
*>*
)[
textInputPlugin
.
textInputV
iews
filteredArrayUsingPredicate:
[
NSPredicate
predicateWithFormat
:
@"self isKindOfClass: %@"
,
[
FlutterTextInputView
class
]]];
}
...
...
@@ -743,4 +783,39 @@ FLUTTER_ASSERT_ARC
[
self
commitAutofillContextAndVerify
];
}
#pragma mark - Accessibility - Tests
-
(
void
)
testUITextInputAccessibilityNotHiddenWhenShowed
{
// Send show text input method call.
[
self
setTextInputShow
];
// Find all the FlutterTextInputViews we created.
NSArray
<
FlutterTextInputView
*>*
inputFields
=
self
.
installedInputViews
;
// The input view should not be hidden.
XCTAssertEqual
([
inputFields
count
],
1u
);
// Send hide text input method call.
[
self
setTextInputHide
];
inputFields
=
self
.
installedInputViews
;
// The input view should be hidden.
XCTAssertEqual
([
inputFields
count
],
0u
);
}
-
(
void
)
testFlutterTextInputViewDirectFocusToBackingTextInput
{
FlutterTextInputViewSpy
*
inputView
=
[[
FlutterTextInputViewSpy
alloc
]
init
];
inputView
.
textInputDelegate
=
engine
;
UIView
*
container
=
[[
UIView
alloc
]
init
];
UIAccessibilityElement
*
backing
=
[[
UIAccessibilityElement
alloc
]
initWithAccessibilityContainer
:
container
];
inputView
.
backingTextInputAccessibilityObject
=
backing
;
// Simulate accessibility focus.
inputView
.
isAccessibilityFocused
=
YES
;
[
inputView
accessibilityElementDidBecomeFocused
];
XCTAssertEqual
(
inputView
.
receivedNotification
,
UIAccessibilityScreenChangedNotification
);
XCTAssertEqual
(
inputView
.
receivedNotificationTarget
,
backing
);
}
@end
shell/platform/darwin/ios/framework/Source/accessibility_text_entry.mm
浏览文件 @
b3a18f51
...
...
@@ -198,10 +198,14 @@ static const UIAccessibilityTraits UIAccessibilityTraitUndocumentedEmptyLine = 0
-
(
void
)
setSemanticsNode
:(
const
flutter
::
SemanticsNode
*
)
node
{
[
super
setSemanticsNode
:
node
];
_inactive_text_input
.
text
=
@
(
node
->
value
.
data
());
FlutterTextInputView
*
textInput
=
(
FlutterTextInputView
*
)[
self
bridge
]
->
textInputView
();
if
([
self
node
].
HasFlag
(
flutter
::
SemanticsFlags
::
kIsFocused
))
{
textInput
.
backingTextInputAccessibilityObject
=
self
;
// 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
);
textInput
.
frame
=
CGRectMake
(
0.0
,
0.0
,
1.0
,
1.0
);
}
else
if
(
textInput
.
backingTextInputAccessibilityObject
==
self
)
{
textInput
.
backingTextInputAccessibilityObject
=
nil
;
}
}
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录