-
-
Save louy/6b66c45ae47bb4e3bac5a104dd0649ff to your computer and use it in GitHub Desktop.
/** | |
* @author Louay Alakkad (github.com/louy) | |
* @license MIT https://opensource.org/licenses/MIT | |
*/ | |
import React from 'react' | |
import PropTypes from 'prop-types' | |
import { | |
NativeModules, | |
ViewProps, | |
ViewPropTypes, | |
findNodeHandle, | |
requireNativeComponent, | |
Platform, | |
View | |
} from 'react-native' | |
const { RNAccessibilityWrapperManager } = NativeModules | |
const RNAccessibilityWrapper = requireNativeComponent( | |
'RNAccessibilityWrapper' | |
) as React.ComponentClass<any> | |
interface AccessibilityWrapperProps extends ViewProps { | |
fieldsRefs?: React.RefObject<React.Component>[] | |
} | |
const AccessibilityWrapperPropTypes = { | |
...ViewPropTypes, | |
fieldsRefs: PropTypes.arrayOf(PropTypes.shape({ | |
current: PropTypes.object | |
}) as PropTypes.Validator<React.RefObject<React.Component>>) | |
} | |
class AccessibilityWrapperIOS extends React.Component< | |
AccessibilityWrapperProps | |
> { | |
public static propTypes = AccessibilityWrapperPropTypes | |
private ref = React.createRef<React.Component<any>>() | |
public componentDidMount() { | |
if (this.props.fieldsRefs) { | |
this.setAccessibilityFields(this.props.fieldsRefs.map(ref => ref.current)) | |
} | |
} | |
public componentDidUpdate() { | |
if (this.props.fieldsRefs) { | |
this.setAccessibilityFields(this.props.fieldsRefs.map(ref => ref.current)) | |
} | |
} | |
private setAccessibilityFields = ( | |
fields: (React.Component<any> | null)[] | |
) => { | |
const fieldTags = | |
fields && fields.map(component => component && findNodeHandle(component)) | |
return RNAccessibilityWrapperManager.setAccessibilityFields( | |
findNodeHandle(this.ref.current), | |
fieldTags | |
) | |
} | |
public render() { | |
return <RNAccessibilityWrapper {...this.props} ref={this.ref} /> | |
} | |
} | |
const AccessibilityWrapperAndroid: React.FunctionComponent< | |
AccessibilityWrapperProps | |
> = ({ fieldsRefs, ...props }) => <View {...props} /> | |
AccessibilityWrapperAndroid.propTypes = AccessibilityWrapperPropTypes | |
/** | |
* The AccessibilityWrapper component allows you to adjust the behaviour of the native platform | |
* when it comes to accessibility. Using this component you can tell the native platform to | |
* group all subviews together for accessibility purposes (since it's not always done by | |
* default), and you can even override the focus order of subviews | |
* | |
* @example | |
* export default class App extends Component<{}> { | |
* fooRef = React.createRef<Text>(); | |
* barRef = React.createRef<Text>(); | |
* bazRef = React.createRef<Text>(); | |
* | |
* public render() { | |
* return ( | |
* <AccessibilityWrapper fieldsRefs={[ | |
* this.barRef, | |
* this.fooRef, | |
* this.bazRef, | |
* ]}> | |
* <SafeAreaView> | |
* <Text ref={this.fooRef}>Foo</Text> | |
* <Text ref={this.barRef}>Bar</Text> | |
* <Text ref={this.bazRef}>Baz</Text> | |
* </SafeAreaView> | |
* </AccessibilityWrapper> | |
* ); | |
* } | |
* } | |
*/ | |
export default Platform.select<React.ComponentType<AccessibilityWrapperProps>>({ | |
ios: AccessibilityWrapperIOS as React.ComponentType< | |
AccessibilityWrapperProps | |
>, | |
android: AccessibilityWrapperAndroid as React.ComponentType< | |
AccessibilityWrapperProps | |
> | |
}) |
// | |
// RNAccessibilityWrapper.h | |
// | |
// Created by Louay Alakkad on 10/04/2019. | |
// License: MIT https://opensource.org/licenses/MIT | |
// | |
#import <UIKit/UIKit.h> | |
#import <UIKit/UIAccessibilityContainer.h> | |
#import <React/RCTView.h> | |
@interface RNAccessibilityWrapper : RCTView | |
- (void) setAccessibilityFields: (NSArray *)reactTags; | |
@end |
// | |
// RNAccessibilityWrapper.m | |
// | |
// Created by Louay Alakkad on 10/04/2019. | |
// License: MIT https://opensource.org/licenses/MIT | |
// | |
#import <Foundation/Foundation.h> | |
#import "RNAccessibilityWrapper.h" | |
#import <UIKit/UIKit.h> | |
@implementation RNAccessibilityWrapper | |
- (void) setAccessibilityFields: (NSArray *)fields | |
{ | |
NSMutableArray *accessibleElements = [NSMutableArray arrayWithCapacity:[fields count]]; | |
[fields enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL * stop) { | |
UIView *field = obj; | |
[accessibleElements addObject:field]; | |
}]; | |
self.accessibilityElements = (NSArray *)accessibleElements; | |
} | |
- (bool) shouldGroupAccessibilityChildren { | |
return YES; | |
} | |
@end |
// | |
// RNAccessibilityViewManager | |
// | |
// Created by Louay Alakkad on 10/04/2019. | |
// License: MIT https://opensource.org/licenses/MIT | |
// | |
#import <React/RCTViewManager.h> | |
@interface RNAccessibilityWrapperManager : RCTViewManager | |
@end |
// | |
// RNAccessibilityWrapper.m | |
// | |
// Created by Louay Alakkad on 10/04/2019. | |
// License: MIT https://opensource.org/licenses/MIT | |
// | |
#import <Foundation/Foundation.h> | |
#import <React/RCTUIManager.h> | |
#import "RNAccessibilityWrapper.h" | |
#import "RNAccessibilityWrapperManager.h" | |
@implementation RNAccessibilityWrapperManager | |
RCT_EXPORT_MODULE() | |
- (UIView *)view { | |
return [[RNAccessibilityWrapper alloc] init]; | |
} | |
RCT_EXPORT_METHOD(setAccessibilityFields:(nonnull NSNumber *)reactTag | |
fieldsReactTags: (nonnull NSArray *)fieldsReactTags) { | |
dispatch_async(dispatch_get_main_queue(), ^{ | |
RNAccessibilityWrapper *component = (RNAccessibilityWrapper *)[self.bridge.uiManager viewForReactTag:reactTag]; | |
NSMutableArray *fields = [NSMutableArray arrayWithCapacity:[fieldsReactTags count]]; | |
[fieldsReactTags enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL * stop) { | |
NSNumber *tag = (NSNumber *)obj; | |
UIView *field = [self.bridge.uiManager viewForReactTag:tag]; | |
[fields addObject:field]; | |
}]; | |
[component setAccessibilityFields: fields]; | |
}); | |
} | |
@end |
@danielrvt the RNAccessibilityWrapperManager.m/h
and RNAccessibilityWrapper.m/h
files would go somewhere under <project_root>/ios/<project_name>
directory, assuming you are using a "bare" RN or ejected Expo project. You'd want to boot up Xcode, opening up the <project_name>.xcworkspace
file. From there you can manually add them to the project. Once added, you'll have to re-build the iOS project - either by running yarn ios
(aka react-native run-ios
) or building in Xcode.
The AccessibilityWrapper.tsx
file would go where you prefer under <project_root>/src
.
Thanks! It worked perfectly!
Is there an equivalent for Android as well?
Hello @louy
I am facing a problem with the custom order if the View renders like this in AccessbilityWrapper :
Bar
Baz
Foo
but voice over order should be like this
Bar
Foo
Baz
I am changing the order of fieldsRefs but not working as expected. It is the same as render
Please help me with this
Is there an equivalent for Android as well?
Android didn't have voice over controls when I did this, but things might have changed now, IDK sadly
@rahulpunchh I'm no longer maintaining this I'm afraid, so I can't help, but you can try asking on stackoverflow or somewhere similar
Hi, tnx for your work man. Probably it's why I can fix bugs.
I wrote a lib, it can be usefull I guess https://www.npmjs.com/package/react-native-a11y (NewArch, Android, iOs ) supported
Hi, tnx for your work man. Probably it's why I can fix bugs. I wrote a lib, it can be usefull I guess https://www.npmjs.com/package/react-native-a11y (NewArch, Android, iOs ) supported
Nice @ArturKalach !! Will check this out. Focus order has long been a pain point with RN and some layouts.
Hello everyone,
I have finished working on an "advanced" ordering system for React Native:
- https://github.com/ArturKalach/react-native-a11y-order
- https://www.npmjs.com/package/react-native-a11y-order
Ordering | Grouping |
---|---|
|
|
It has some nuances, but honestly, it works really well!
API is really simple, we could control the order by web way via indexes.
<A11y.Order>
<A11y.Index index={1}>
<Text style={styles.font}>
First
</Text>
</A11y.Index>
<A11y.Index index={3}>
<Text style={styles.font}>
Third
</Text>
</A11y.Index>
<A11y.Index index={2}>
<Text style={styles.font}>
Second
</Text>
</A11y.Index>
</A11y.Order>
NO ScrollView grouping
Additionally, there's no need to group content using ScrollView
for horizontal scrolls anymore. A11y.Group
functions almost like a bare View, but, it achieves the same effect
@ArturKalach Nice work! Will definitely implement it in my projects. Have needed this for a long time.
@ArturKalach Nice work! Will definitely implement it in my projects. Have needed this for a long time.
@AdamGerthel
Thanks a lot! I spent weeks finishing this, and your words show that it wasn't a waste of time)
The native and new architecture are really tricky, but it's been a good experience. There is still some work to be done, but the API is complete. There's still a lot of work left, but I really enjoy the results.
P.S. I think this page and the work of @louy were a starting point.
Hi @louy thanks for sharing this! I'm relatively new to react native and I'm not sure how to use these files. Do I just compy them into the project folder?