1+ //
2+ // BSTextAccessibility.swift
3+ // BSText 3.0
4+ //
5+ // Accessibility support for BSText components.
6+ //
7+
8+ import UIKit
9+
10+ /// Accessibility extensions for BSTextView.
11+ extension BSTextView {
12+
13+ /// Configures accessibility settings for the text view.
14+ public func configureAccessibility( ) {
15+ isAccessibilityElement = true
16+ accessibilityLabel = " Text editor "
17+ accessibilityHint = " Double tap to edit "
18+ accessibilityTraits = . staticText
19+ }
20+
21+ /// Updates accessibility label with current text content.
22+ public func updateAccessibilityLabel( ) {
23+ let textLength = text. count
24+ if textLength > 0 {
25+ let preview = text. prefix ( 50 ) + ( textLength > 50 ? " ... " : " " )
26+ accessibilityLabel = " \( preview) ( \( textLength) characters) "
27+ } else {
28+ accessibilityLabel = " Empty text editor "
29+ }
30+ }
31+
32+ /// Posts accessibility notification when text changes.
33+ public func postTextChangeNotification( ) {
34+ UIAccessibility . post ( notification: . layoutChanged, argument: self )
35+ }
36+ }
37+
38+ /// Accessibility extensions for BSTextCodeEditor.
39+ extension BSTextCodeEditor {
40+
41+ /// Configures accessibility settings for the code editor.
42+ public override func configureAccessibility( ) {
43+ isAccessibilityElement = true
44+ accessibilityLabel = " Code editor "
45+ accessibilityHint = " Double tap to edit code "
46+ accessibilityTraits = . staticText
47+ }
48+ }
49+
50+ /// Accessibility container for text attachments.
51+ @objcMembers
52+ public class BSTextAttachmentAccessibilityElement : UIAccessibilityElement {
53+
54+ /// The attachment this element represents.
55+ public weak var attachment : BSTextAttachment ?
56+
57+ /// The text view containing this attachment.
58+ public weak var textView : UIView ?
59+
60+ public init ( accessibilityContainer: Any , attachment: BSTextAttachment , textView: UIView ) {
61+ super. init ( accessibilityContainer: accessibilityContainer)
62+ self . attachment = attachment
63+ self . textView = textView
64+ configure ( )
65+ }
66+
67+ private func configure( ) {
68+ guard let attachment = attachment else {
69+ accessibilityLabel = " Attachment "
70+ accessibilityTraits = . staticText
71+ return
72+ }
73+
74+ switch attachment. attachmentType {
75+ case . image:
76+ accessibilityLabel = " Image attachment "
77+ accessibilityTraits = . image
78+
79+ case . animatedImage:
80+ accessibilityLabel = " Animated image "
81+ accessibilityTraits = . image
82+
83+ case . video:
84+ accessibilityLabel = " Video attachment "
85+ accessibilityTraits = . staticText
86+
87+ case . view:
88+ accessibilityLabel = " View attachment "
89+ accessibilityTraits = . staticText
90+
91+ case . swiftUI:
92+ accessibilityLabel = " SwiftUI attachment "
93+ accessibilityTraits = . staticText
94+
95+ default :
96+ accessibilityLabel = " Attachment "
97+ accessibilityTraits = . staticText
98+ }
99+ }
100+ }
101+
102+ /// Accessibility delegate for BSTextView.
103+ @objc public protocol BSTextAccessibilityDelegate : AnyObject {
104+
105+ /// Called when accessibility element needs to be created for an attachment.
106+ ///
107+ /// - Parameters:
108+ /// - textView: The text view.
109+ /// - attachment: The attachment.
110+ /// - frame: The frame of the attachment.
111+ /// - Returns: An accessibility element for the attachment.
112+ @objc optional func textView( _ textView: BSTextView , accessibilityElementFor attachment: BSTextAttachment , frame: CGRect ) -> BSTextAttachmentAccessibilityElement ?
113+ }
114+
115+ /// Accessibility helper for managing attachment accessibility.
116+ @objcMembers
117+ public class BSTextAccessibilityManager : NSObject {
118+
119+ /// The text view being managed.
120+ public weak var textView : BSTextView ?
121+
122+ /// Accessibility elements for attachments.
123+ public private( set) var attachmentElements : [ BSTextAttachmentAccessibilityElement ] = [ ]
124+
125+ public init ( textView: BSTextView ) {
126+ self . textView = textView
127+ super. init ( )
128+ }
129+
130+ /// Updates accessibility elements for current attachments.
131+ public func updateAccessibilityElements( ) {
132+ attachmentElements. removeAll ( )
133+
134+ guard let textView = textView else {
135+ return
136+ }
137+
138+ let textStorage = textView. textStorage
139+ textStorage. enumerateAttribute ( . attachment, in: NSRange ( location: 0 , length: textStorage. length) ) { value, range, _ in
140+ if let attachment = value as? BSTextAttachment {
141+ let element = BSTextAttachmentAccessibilityElement (
142+ accessibilityContainer: textView,
143+ attachment: attachment,
144+ textView: textView
145+ )
146+ attachmentElements. append ( element)
147+ }
148+ }
149+ }
150+
151+ /// Returns all accessibility elements.
152+ public func attachmentAccessibilityElements( ) -> [ Any ] {
153+ return attachmentElements
154+ }
155+ }
156+
157+ /// Dynamic Type support extensions.
158+ extension BSTextView {
159+
160+ /// Enables dynamic type support.
161+ public func enableDynamicType( ) {
162+ NotificationCenter . default. addObserver (
163+ self ,
164+ selector: #selector( handleContentSizeCategoryChange) ,
165+ name: UIContentSizeCategory . didChangeNotification,
166+ object: nil
167+ )
168+ }
169+
170+ /// Disables dynamic type support.
171+ public func disableDynamicType( ) {
172+ NotificationCenter . default. removeObserver (
173+ self ,
174+ name: UIContentSizeCategory . didChangeNotification,
175+ object: nil
176+ )
177+ }
178+
179+ @objc private func handleContentSizeCategoryChange( ) {
180+ // Update font based on content size category
181+ let preferredFont = UIFont . preferredFont ( forTextStyle: . body)
182+ font = preferredFont
183+ }
184+ }
0 commit comments