SwiftKickMobile / SwiftMessages

A very flexible message bar for UIKit and SwiftUI.
MIT License
7.3k stars 742 forks source link

Accessibility for View Controllers Presented Via SwiftMessagesSegue #352

Closed patmalt closed 4 years ago

patmalt commented 4 years ago

SwiftMessagesSegue's messageView is a BaseView, and since BaseView does not conform to AccessibleMessage, presented view controllers will not be accessible via Accessibility Inspector or Voice Over.

I implemented a subclass of BaseView that does conform to AccessibleMessage, and set it to the messageView property of SwiftMessagesSegue. This should work in most cases...

Expect where the presented view controller is wrapped inside of a UINavigationController.

During the install phase of Presenter, the UINavigationController's rootViewController is not added to the messageView's view hierarchy yet, and the elements are also not exposed.

Any thoughts or suggestions?

wtmoose commented 4 years ago

I’m not sure I’d use AccessibleMessage with view controllers at all due to their complexity. AccessibleMessage was meant to help improve voice over for simple views, such as reading the title + body out as a single unit. Was there a specific reason you wanted to use AccessibleMessage?

patmalt commented 4 years ago

Yes, so we can use Voice Over on view controllers we present with SwiftMessagesSegue. All we get now is the large "Dismiss" area

wtmoose commented 4 years ago

Hmm. I think ideally you’d get the view controller’s default voice over behavior. I’ll need to look into it.

patmalt commented 4 years ago

I do not believe this to be true

dtashkandi commented 4 years ago

@patmalt can you please share your subclass of BaseView?

patmalt commented 4 years ago
import SwiftMessages

class SwiftMessagesBottomSegue: SwiftMessagesSegue {
    init(source: UIViewController, destination: UIViewController) {
        super.init(identifier: nil, source: source, destination: destination)
        configure(layout: .bottomCard)
        self.messageView = MyNewBaseView(destination)
    }
}

class MyNewBaseView: BaseView, AccessibleMessage {
    var uiViewController: UIViewController?

    init(_ destination: UIViewController) {
        uiViewController = destination
        super.init(frame: destination.view.frame)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    var accessibilityMessage: String? = "My Bottom Sheet"
    open var additonalAccessibilityElements: [NSObject]? {
        var elements: [NSObject] = []
        func getAccessibleSubviews(view: UIView) {
            for subview in view.subviews {
                if subview.isAccessibilityElement {
                    elements.append(subview)
                } else {
                    getAccessibleSubviews(view: subview)
                }
            }
        }
        getAccessibleSubviews(view: self.backgroundView)
        return elements
    }
    var accessibilityElement: NSObject?
}

I believe I copied the accessibility code from another part of SwiftMessages

dtashkandi commented 4 years ago

@patmalt thanks!

wtmoose commented 4 years ago

@patmalt @dtashkandi I pushed a fix for this to the head of master if you want to try it out and let me know.

Previously, if a view that didn't adopt AccessibleMessage was ignored by Voice Over. With meh fix, these views are not ignored by Voice Over and are responsible for their own accessibility. I think this is the ideal behavior for view controllers. I don't recommend putting view controllers behind the AccessibleMessage protocol, but you can do that if you want to (as you've done above).

patmalt commented 4 years ago

This fix worked for me! Thanks. I think we can consider this issue closed.

wtmoose commented 4 years ago

Great. Will close when I release the change.

wtmoose commented 4 years ago

Included in 8.0.0 release