Skip to content

Crash after providing accessibilityScrollStatus(for scrollView: UIScrollView) #398

Open
@Ma-He

Description

@Ma-He

We are trying to override the default iOS VoiceOver read out during the scrolling of a UITableView with RxTableViewSectionedReloadDataSource binded to it.
After extending our ViewController to UIScrollViewAccessibilityDelegate and implementing

func accessibilityScrollStatus(for scrollView: UIScrollView) -> String? {
    return "Some status"
}

we will receive a crash when VoiceOver is enabled and we are trying to scroll the list.

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[RxCocoa.RxTableViewDelegateProxy accessibilityScrollStatusForScrollView:]: unrecognized selector sent to instance 0x282a966f0'

To recreate the error, you can simply display this VoiceOverScrollStatusViewController and scroll down the list with VoiceOver enabled.

import Foundation
import UIKit
import RxSwift
import RxCocoa
import RxDataSources

struct CustomData {
    var anInt: Int
    var aString: String
}

struct SectionOfCustomData {
    var header: String
    var items: [Item]
}

extension SectionOfCustomData: SectionModelType {
    typealias Item = CustomData
    
    init(original: SectionOfCustomData, items: [Item]) {
        self = original
        self.items = items
    }
}

class VoiceOverScrollStatusViewController: UIViewController {
    private let tableView: UITableView
    private let disposeBag = DisposeBag()
    
    let rxDataSource = RxTableViewSectionedReloadDataSource<SectionOfCustomData> { (_, tableView, _, itemSource) -> UITableViewCell in
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") else {
            return UITableViewCell()
        }
        
        cell.textLabel?.text = "Cell \(itemSource.anInt): \(itemSource.aString)"
        return cell
    }
    
    init() {
        if #available(iOS 13.0, *) {
            tableView = UITableView(frame: .zero, style: .insetGrouped)
        } else {
            tableView = UITableView(frame: .zero, style: .grouped)
        }
        
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    // MARK: - view lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupTableView()
        
        rxDataSource.titleForHeaderInSection = { dataSource, index in
            return dataSource.sectionModels[index].header
        }
        
        let sections = [
            SectionOfCustomData(header: "First Section", items: [.init(anInt: 1, aString: "First"),
                                                                 .init(anInt: 2, aString: "Zweiter"),
                                                                 .init(anInt: 3, aString: "Third")]),
            SectionOfCustomData(header: "Second Section", items: [.init(anInt: 1, aString: "First"),
                                                                 .init(anInt: 2, aString: "Zweiter")]),
            SectionOfCustomData(header: "Third Section", items: [.init(anInt: 1, aString: "First"),
                                                                 .init(anInt: 2, aString: "Zweiter"),
                                                                 .init(anInt: 3, aString: "Third")]),
            SectionOfCustomData(header: "Fourth Section", items: [.init(anInt: 1, aString: "First")]),
            SectionOfCustomData(header: "Fifth Section", items: [.init(anInt: 1, aString: "First"),
                                                                 .init(anInt: 2, aString: "Second"),
                                                                 .init(anInt: 3, aString: "Third")])
        ]
        
        Observable.just(sections)
            .bind(to: tableView.rx.items(dataSource: rxDataSource))
            .disposed(by: disposeBag)
    }
    
    private func setupTableView() {
        tableView.backgroundColor = UIColor(hex: "#F3F5F8")
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
        tableView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(tableView)
        
        if #available(iOS 11.0, *) {
            let guide = view.safeAreaLayoutGuide
            tableView.topAnchor.constraint(equalTo: guide.topAnchor).isActive = true
            tableView.bottomAnchor.constraint(equalTo: guide.bottomAnchor).isActive = true
            tableView.leadingAnchor.constraint(equalTo: guide.leadingAnchor).isActive = true
            tableView.trailingAnchor.constraint(equalTo: guide.trailingAnchor).isActive = true
        } else {
            tableView.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor).isActive = true
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        }
        
        tableView.delegate = self
    }
}

extension VoiceOverScrollStatusViewController: UITableViewDelegate {
    
}

extension VoiceOverScrollStatusViewController: UIScrollViewAccessibilityDelegate {
    func accessibilityScrollStatus(for scrollView: UIScrollView) -> String? {
        return "Some status"
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions