无法为自定义 UITableViewCell 配置语音辅助功能

Unable to configure voice over accessibility for a custom UITableViewCell

我们的 iPhone 应用目前支持 IOS 8/9/10。我很难支持自定义 UITableViewCell 的语音辅助功能。我浏览了以下 SO 帖子,但 none 的建议已经奏效。我希望可以访问各个组件。

  1. Custom UITableview cell accessibility not working correctly
  2. Custom UITableViewCell trouble with UIAccessibility elements
  3. Accessibility in custom drawn UITableViewCell
  4. https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/iPhoneAccessibility/Making_Application_Accessible/Making_Application_Accessible.html#//apple_ref/doc/uid/TP40008785-CH102-SW10
  5. http://useyourloaf.com/blog/voiceover-accessibility/

对我来说不幸的是,可访问性检查器没有检测到该单元格。有没有一种方法可以通过可访问性来选择 table 视图单元格中的单个元素?在设备和模拟器上调试这个问题时,我发现 XCode 调用了 isAccessibleElement 函数。当函数returnsNO时,则跳过其余方法。我正在 XCode 中的 IOS 9.3 上进行测试。

我的自定义 table 视图单元格由一个标签和一个开关组成,如下所示。

标签添加到内容视图,开关添加到自定义附件视图。

接口定义如下

@interface MyCustomTableViewCell : UITableViewCell

///Designated initializer
- (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier;


///Property that determines if the switch displayed in the cell is ON or OFF.
@property (nonatomic, assign) BOOL switchIsOn;

///The label to be displayed for the alert
@property (nonatomic, strong) UILabel *alertLabel;

@property (nonatomic, strong) UISwitch *switch;

#pragma mark - Accessibility
// Used for setting up accessibility values. This is used to generate accessibility labels of
// individual elements.
@property (nonatomic, strong) NSString* accessibilityPrefix;

-(void)setAlertHTMLText:(NSString*)title;


@end

下面给出了实现块

@interface MyCustomTableViewCell()

    @property (nonatomic, strong) UIView *customAccessoryView;
    @property (nonatomic, strong) NSString *alertTextString;
    @property (nonatomic, strong) NSMutableArray* accessibleElements;
@end

@implementation MyCustomTableViewCell


    - (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier
    {
       if(self = [super initWithStyle:UITableViewCellStyleDefault        
             reuseIdentifier:reuseIdentifier]) {
           [self configureTableCell];
       }
       return self;
    }


    - (void)configureTableCell
    {
      if (!_accessibleElements) {
          _accessibleElements = [[NSMutableArray alloc] init];
      }


    //Alert label
    self.alertLabel = [[self class] makeAlertLabel];
    [self.contentView setIsAccessibilityElement:YES];
 // 
   [self.contentView addSubview:self.alertLabel];

   // Custom AccessoryView for easy styling.
  self.customAccessoryView = [[UIView alloc] initWithFrame:CGRectZero];
  [self.customAccessoryView setIsAccessibilityElement:YES];
  [self.contentView addSubview:self.customAccessoryView];

 //switch
  self.switch = [[BAUISwitch alloc] initWithFrame:CGRectZero];
  [self.switch addTarget:self action:@selector(switchWasFlipped:) forControlEvents:UIControlEventValueChanged];
 [self.switch setIsAccessibilityElement:YES];
 [self.switch setAccessibilityTraits:UIAccessibilityTraitButton];
 [self.switch setAccessibilityLabel:@""];
 [self.switch setAccessibilityHint:@""];
 self.switch.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
 [self.customAccessoryView addSubview:self.switch];
}

+ (UILabel *)makeAlertLabel
{
  UILabel *alertLabel = [[UILabel alloc] initWithFrame:CGRectZero];
  alertLabel.backgroundColor = [UIColor clearColor];
  alertLabel.HTMLText = @"";
  alertLabel.numberOfLines = 0;
  alertLabel.lineBreakMode = LINE_BREAK_WORD_WRAP
  [alertLabel setIsAccessibilityElement:YES];
  return alertLabel;
}

-(void)setAlertHTMLText:(NSString*)title{ 
    _alertTextString = [NSString stringWithString:title];
    [self.alertLabel setText:_alertTextString];
}


- (BOOL)isAccessibilityElement {
    return NO;
}

  // The view encapsulates the following elements for the purposes of    
 // accessibility.
-(NSArray*) accessibleElements {
  if (_accessibleElements && [_accessibleElements count] > 0) {
      [_accessibleElements removeAllObjects];
  }
  // Fetch a new copy as the values may have changed.
  _accessibleElements = [[NSMutableArray alloc] init];
  UIAccessibilityElement* alertLabelElement =
  [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self];
  //alertLabelElement.accessibilityFrame = [self convertRect:self.contentView.frame toView:nil];
  alertLabelElement.accessibilityLabel = _alertTextString;
  alertLabelElement.accessibilityTraits = UIAccessibilityTraitStaticText;
  [_accessibleElements addObject:alertLabelElement];


    UIAccessibilityElement* switchElement =
        [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self];
  //  switchElement.accessibilityFrame = [self convertRect:self.customAccessoryView.frame toView:nil];
    switchElement.accessibilityTraits = UIAccessibilityTraitButton;
    // If you want custom values, just override it in the invoking function.
    NSMutableString* accessibilityString =
        [NSMutableString stringWithString:self.accessibilityPrefix];
    [accessibilityString appendString:@" Switch "];
    if (self.switchh.isOn) {
        [accessibilityString appendString:@"On"];
    } else {
        [accessibilityString appendString:@"Off"];
    }
    switchElement.accessibilityLabel = [accessibilityString copy];

    [_accessibleElements addObject:switchElement];
  }
  return _accessibleElements;
}

// In case accessibleElements is not initialized.
- (void) initializeAccessibleElements {
   _accessibleElements = [self accessibleElements];
}

- (NSInteger)accessibilityElementCount
{
    return [_accessibleElements count]
}

- (id)accessibilityElementAtIndex:(NSInteger)index
  {
    [self initializeAccessibleElements];
    return [_accessibleElements objectAtIndex:index];
 }

 - (NSInteger)indexOfAccessibilityElement:(id)element
 {
    [self initializeAccessibleElements];
    return [_accessibleElements indexOfObject:element];
}
@end

首先,根据您描述的模式,我不确定您为什么要区分单元格中的不同元素。通常,Apple 将每个单元格都保留为一个可访问性元素。设置应用程序中是查看带有标签和开关的单元格的预期 iOS VO 行为的好地方。

如果您仍然认为处理单元格的最佳方式是让它们包含单独的元素,那么这实际上是单元格的默认行为 UITableViewCell 本身不存在时有无障碍标签。所以,我修改了下面的代码,并在我的 iOS 设备 (运行ning 9.3) 上 运行 修改了它,它按照你描述的方式工作。

您会注意到一些事情。

  1. 我删除了所有自定义 accessibilityElements 代码。没必要。
  2. 我删除了 UITableViewCell 子类本身的 isAccessibilityElement 覆盖。我们想要默认行为。
  3. 我注释掉了将内容视图设置为 accessibilityElement——我们希望它为 NO,以便树构建器在其中查找元素。
  4. 出于与上述相同的原因,我也将 customAccessoryView 的 isAccessibilityElement 设置为 NO。通常,NO 表示 "keep looking down the tree",YES 表示 "stop here, this is my leaf as far as accessibility is concerned."

希望对您有所帮助。再一次,我确实鼓励您在设计辅助功能时模仿 Apple 的 VO 模式。我认为您确保您的应用程序可供访问,这很棒!

#import "MyCustomTableViewCell.h"

@interface MyCustomTableViewCell()

@property (nonatomic, strong) UIView *customAccessoryView;
@property (nonatomic, strong) NSString *alertTextString;
@property (nonatomic, strong) NSMutableArray* accessibleElements;
@end

@implementation MyCustomTableViewCell

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    if(self = [super initWithStyle:UITableViewCellStyleDefault
                   reuseIdentifier:reuseIdentifier]) {
        [self configureTableCell];
    }
    return self;
}

// just added this here to get the cell to lay out for myself
- (void)layoutSubviews {
    [super layoutSubviews];

    const CGFloat margin = 8;

    CGRect b = self.bounds;

    CGSize labelSize = [self.alertLabel sizeThatFits:b.size];
    CGFloat maxX = CGRectGetMaxX(b);
    self.alertLabel.frame = CGRectMake(margin, margin, labelSize.width, labelSize.height);

    CGSize switchSize = [self.mySwitch sizeThatFits:b.size];
    self.customAccessoryView.frame = CGRectMake(maxX - switchSize.width - margin * 2, b.origin.y + margin, switchSize.width + margin * 2, switchSize.height);
    self.mySwitch.frame = CGRectMake(margin, 0, switchSize.width, switchSize.height);
}


- (void)configureTableCell
{
    //Alert label
    self.alertLabel = [[self class] makeAlertLabel];
    //[self.contentView setIsAccessibilityElement:YES];
    //
    [self.contentView addSubview:self.alertLabel];

    // Custom AccessoryView for easy styling.
    self.customAccessoryView = [[UIView alloc] initWithFrame:CGRectZero];
    [self.customAccessoryView setIsAccessibilityElement:NO]; // Setting this to NO tells the the hierarchy builder to look inside
    [self.contentView addSubview:self.customAccessoryView];
    self.customAccessoryView.backgroundColor = [UIColor purpleColor];

    //switch
    self.mySwitch = [[UISwitch alloc] initWithFrame:CGRectZero];
    //[self.mySwitch addTarget:self action:@selector(switchWasFlipped:) forControlEvents:UIControlEventValueChanged];
    [self.mySwitch setIsAccessibilityElement:YES]; // This is default behavior
    [self.mySwitch setAccessibilityTraits:UIAccessibilityTraitButton]; // No tsure why this is here
    [self.mySwitch setAccessibilityLabel:@"my swich"];
    [self.mySwitch setAccessibilityHint:@"Tap to do something."];
    self.mySwitch.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
    [self.customAccessoryView addSubview:self.mySwitch];
}

+ (UILabel *)makeAlertLabel
{
    UILabel *alertLabel = [[UILabel alloc] initWithFrame:CGRectZero];
    alertLabel.backgroundColor = [UIColor clearColor];
    alertLabel.text = @"";
    alertLabel.numberOfLines = 0;
    [alertLabel setIsAccessibilityElement:YES];
    return alertLabel;
}

-(void)setAlertHTMLText:(NSString*)title{
    _alertTextString = [NSString stringWithString:title];
    [self.alertLabel setText:_alertTextString];
}

@end