diff --git a/VDSSample.xcodeproj/project.pbxproj b/VDSSample.xcodeproj/project.pbxproj index 6bb1d06..4d16f0b 100644 --- a/VDSSample.xcodeproj/project.pbxproj +++ b/VDSSample.xcodeproj/project.pbxproj @@ -48,6 +48,7 @@ EA3C3BB528996775000CA526 /* StoryboardInitable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3C3BB128996775000CA526 /* StoryboardInitable.swift */; }; EA3C3BB628996775000CA526 /* MenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3C3BB228996775000CA526 /* MenuViewController.swift */; }; EA3C3BB728996775000CA526 /* ToggleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3C3BB328996775000CA526 /* ToggleViewController.swift */; }; + EA471F402A97BEAA00CE9E58 /* CustomRotorable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA471F3F2A97BEAA00CE9E58 /* CustomRotorable.swift */; }; EA4DB30428DCD25B00103EE3 /* BadgeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA4DB30328DCD25B00103EE3 /* BadgeViewController.swift */; }; EA596ABA2A16B2ED00300C4B /* TabsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA596AB92A16B2ED00300C4B /* TabsViewController.swift */; }; EA5E3050294D11540082B959 /* TileContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5E304F294D11540082B959 /* TileContainerViewController.swift */; }; @@ -144,6 +145,7 @@ EA3C3BB128996775000CA526 /* StoryboardInitable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoryboardInitable.swift; sourceTree = ""; }; EA3C3BB228996775000CA526 /* MenuViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuViewController.swift; sourceTree = ""; }; EA3C3BB328996775000CA526 /* ToggleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToggleViewController.swift; sourceTree = ""; }; + EA471F3F2A97BEAA00CE9E58 /* CustomRotorable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomRotorable.swift; sourceTree = ""; }; EA4DB30328DCD25B00103EE3 /* BadgeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeViewController.swift; sourceTree = ""; }; EA596AB92A16B2ED00300C4B /* TabsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsViewController.swift; sourceTree = ""; }; EA5E304F294D11540082B959 /* TileContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileContainerViewController.swift; sourceTree = ""; }; @@ -294,6 +296,7 @@ EAF7F07E28996A0700B287F5 /* Protocols */ = { isa = PBXGroup; children = ( + EA471F3F2A97BEAA00CE9E58 /* CustomRotorable.swift */, EA3C3BB028996775000CA526 /* PickerBase.swift */, EA3C3BB128996775000CA526 /* StoryboardInitable.swift */, ); @@ -498,6 +501,7 @@ 445BA07A29C088470036A7C5 /* NotificationViewController.swift in Sources */, EAF7F11A28A14A0E00B287F5 /* RadioButtonGroupViewController.swift in Sources */, EA89204628B66CE2006B9984 /* ScrollViewController.swift in Sources */, + EA471F402A97BEAA00CE9E58 /* CustomRotorable.swift in Sources */, EA3C3B9F289966EF000CA526 /* SceneDelegate.swift in Sources */, EA0D1C332A673FD400E5C127 /* RadioButtonItemViewController.swift in Sources */, 5FC35BE928D5235A004EBEAC /* ButtonViewController.swift in Sources */, @@ -679,7 +683,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 39; + CURRENT_PROJECT_VERSION = 40; DEVELOPMENT_TEAM = FCMA4QKS77; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = VDSSample/Info.plist; @@ -711,7 +715,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 39; + CURRENT_PROJECT_VERSION = 40; DEVELOPMENT_TEAM = FCMA4QKS77; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = VDSSample/Info.plist; diff --git a/VDSSample/Protocols/CustomRotorable.swift b/VDSSample/Protocols/CustomRotorable.swift new file mode 100644 index 0000000..66c64e8 --- /dev/null +++ b/VDSSample/Protocols/CustomRotorable.swift @@ -0,0 +1,101 @@ +// +// AccessibilityCustomRotorable.swift +// VDS +// +// Created by Matt Bruce on 8/24/23. +// + +import Foundation +import UIKit + +public protocol CustomRotorable: UIViewController { + var customRotors: [CustomRotorType] { get set } +} + +extension CustomRotorable { + + /// Adds CustomRotor to the accessibilityCustomRotors array. + /// - Parameters: + /// - name: Name that will show up in the Rotor picker. + /// - trait: Any UIView that has this traits will be added to this Name. + internal func addCustomRotor(with name: String, for trait: UIAccessibilityTraits) { + //filter out old rotors with same name + accessibilityCustomRotors = (accessibilityCustomRotors ?? []).filter { $0.name != name } + + //create new rotor + let newRotor = UIAccessibilityCustomRotor(name: name) { [weak self] predicate in + guard let self else { return nil } + + let views = self.view.accessibleElements(with: trait) + + guard !views.isEmpty else { return nil } + + let currentIndex = views.firstIndex(where: { $0 === predicate.currentItem.targetElement }) + let count = views.count + + //find the nextIndex + let nextIndex: Int + switch predicate.searchDirection { + case .next: + if let currentIndex, currentIndex != count - 1{ + //go forwards + nextIndex = currentIndex + 1 + } else { + //get the first + nextIndex = 0 + } + case .previous: + if let currentIndex, currentIndex != 0 { + //go backwards + nextIndex = currentIndex - 1 + } else { + //get the last + nextIndex = count - 1 + } + @unknown default: + //get the first + nextIndex = 0 + } + + return UIAccessibilityCustomRotorItemResult(targetElement: views[nextIndex], targetRange: nil) + } + + //append rotor + accessibilityCustomRotors?.append(newRotor) + } + + /// Loads all of the custom rotors for the screen. + public func loadCustomRotors() { + customRotors.forEach { addCustomRotor(with: $0.name, for: $0.trait) } + } +} + +public extension UIView { + + /// Gets all of the Views that has the matching accessibilityTrait. + /// - Parameter trait: This is the trailt for the accessibilityTrait property of a view. + /// - Returns: An array of RotorItemResult + func accessibleElements(with trait: UIAccessibilityTraits) -> [UIView] { + var elements: [UIView] = [] + + //add your self if you meet the requirements + if isAccessibilityElement, accessibilityTraits.contains(trait) { + elements.append(self) + } + + //loop through your subviews + subviews.forEach { elements.append(contentsOf: $0.accessibleElements(with: trait)) } + + return elements + } +} + +public struct CustomRotorType { + public var name: String + public var trait: UIAccessibilityTraits + + public init(name: String, trait: UIAccessibilityTraits) { + self.name = name + self.trait = trait + } +} diff --git a/VDSSample/ViewControllers/BadgeIndicatorViewController.swift b/VDSSample/ViewControllers/BadgeIndicatorViewController.swift index 20ef79c..b9336a5 100644 --- a/VDSSample/ViewControllers/BadgeIndicatorViewController.swift +++ b/VDSSample/ViewControllers/BadgeIndicatorViewController.swift @@ -211,3 +211,14 @@ class BadgeIndicatorViewController: BaseViewController { } } } + +extension BadgeIndicatorViewController: ComponentSampleable { + static func makeSample() -> ComponentSample { + let component = Self.makeComponent() + component.fillColor = .red + component.number = 23 + component.kind = .simple + component.size = .medium + return ComponentSample(component: component, trailingPinningType: .lessThanOrEqual) + } +} diff --git a/VDSSample/ViewControllers/BadgeViewController.swift b/VDSSample/ViewControllers/BadgeViewController.swift index 7025673..3e37bd9 100644 --- a/VDSSample/ViewControllers/BadgeViewController.swift +++ b/VDSSample/ViewControllers/BadgeViewController.swift @@ -108,13 +108,13 @@ class BadgeViewController: BaseViewController { } } -extension BadgeViewController: Componentable { - static func getComponent() -> TestViewWrapper { +extension BadgeViewController: ComponentSampleable { + static func makeSample() -> ComponentSample { let component = Self.makeComponent() component.fillColor = .red component.text = "Terms and conditions" component.maxWidth = 70 component.numberOfLines = 3 - return TestViewWrapper(component: component, isTrailing: true) + return ComponentSample(component: component, trailingPinningType: .lessThanOrEqual) } } diff --git a/VDSSample/ViewControllers/BaseViewController.swift b/VDSSample/ViewControllers/BaseViewController.swift index 3aadef4..1449e03 100644 --- a/VDSSample/ViewControllers/BaseViewController.swift +++ b/VDSSample/ViewControllers/BaseViewController.swift @@ -66,7 +66,7 @@ public class FormSection: UIStackView { } } -public class BaseViewController: UIViewController, Initable { +public class BaseViewController: UIViewController, Initable , CustomRotorable { deinit { print("\(Self.self) deinit") } @@ -81,6 +81,11 @@ public class BaseViewController: UIViewController, Initable { // MARK: - Combine Properties //-------------------------------------------------- public var subscribers = Set() + + public var customRotors: [CustomRotorType] = [ + CustomRotorType(name: "Links", trait: .link), + CustomRotorType(name: "Buttons", trait: .button) + ] //-------------------------------------------------- // MARK: - Properties @@ -117,6 +122,7 @@ public class BaseViewController: UIViewController, Initable { setup() contentTopView.backgroundColor = Surface.light.color } + } public lazy var surfacePickerSelectorView = { @@ -229,6 +235,8 @@ public class BaseViewController: UIViewController, Initable { self?.activeTextField?.resignFirstResponder() self?.activeTextField = nil }.store(in: &subscribers) + + loadCustomRotors() } func isViewHiddenByKeyboard(view: UIView, keyboardFrame: CGRect) -> Bool { diff --git a/VDSSample/ViewControllers/ButtonGroupViewController.swift b/VDSSample/ViewControllers/ButtonGroupViewController.swift index 7a63b3f..ce8325e 100644 --- a/VDSSample/ViewControllers/ButtonGroupViewController.swift +++ b/VDSSample/ViewControllers/ButtonGroupViewController.swift @@ -118,10 +118,10 @@ class ButtonGroupViewController: BaseViewController { addFormRow(label: "Percentage (1-100)", view: percentageTextField) disabledSwitch.onChange = { [weak self] sender in - self?.largeLabel.disabled = sender.isOn - self?.smallLabel.disabled = sender.isOn - self?.component.disabled = sender.isOn - self?.smallButtonGroup.disabled = sender.isOn + self?.largeLabel.isEnabled = !sender.isOn + self?.smallLabel.isEnabled = !sender.isOn + self?.component.isEnabled = !sender.isOn + self?.smallButtonGroup.isEnabled = !sender.isOn } widthTextField @@ -156,7 +156,7 @@ class ButtonGroupViewController: BaseViewController { //setup UI surfacePickerSelectorView.text = component.surface.rawValue buttonPositionSelectorView.text = component.buttonPosition.rawValue - disabledSwitch.isOn = component.disabled + disabledSwitch.isOn = !component.isEnabled rowQuantitySelectorView.text = RowQuantity(quantity: component.rowQuantity).rawValue widthTextField.text = "" } @@ -198,3 +198,19 @@ class ButtonGroupViewController: BaseViewController { } } } + +extension ButtonGroupViewController: ComponentSampleable { + static func makeSample() -> ComponentSample { + let component = Self.makeComponent() + let onClick: (ButtonBase) -> Void = { button in print("\(button.text!) clicked")} + component.buttons = [ + Button().with{ $0.use = .secondary; $0.text = "Secondary"; $0.onClick = onClick}, + Button().with{ $0.use = .primary; $0.text = "Primary"; $0.onClick = onClick }, + TextLink().with { $0.size = .large; $0.text = "Large Text Link"; $0.onClick = onClick }, + TextLink().with { $0.text = "Widge Label Button"; $0.onClick = onClick }, + TextLinkCaret().with { $0.text = "Text Link Caret"; $0.onClick = onClick} + ] + + return ComponentSample(component: component) + } +} diff --git a/VDSSample/ViewControllers/ButtonIconViewController.swift b/VDSSample/ViewControllers/ButtonIconViewController.swift index 6f31c6b..e055285 100644 --- a/VDSSample/ViewControllers/ButtonIconViewController.swift +++ b/VDSSample/ViewControllers/ButtonIconViewController.swift @@ -75,7 +75,7 @@ class ButtonIconViewController: BaseViewController { addFormRow(label: "Y Offset", view: centerY) disabledSwitch.onChange = { [weak self] sender in - self?.component.disabled = sender.isOn + self?.component.isEnabled = !sender.isOn } component.onClickActionPublisher("ButtonIcon", label: actionLabel) @@ -121,7 +121,7 @@ class ButtonIconViewController: BaseViewController { kindPickerSelectorView.text = component.kind.rawValue sizePickerSelectorView.text = component.size.rawValue namePickerSelectorView.text = name.rawValue - disabledSwitch.isOn = component.disabled + disabledSwitch.isOn = !component.isEnabled } func updateOffset() { @@ -169,3 +169,12 @@ extension UITextField { return CGFloat(double) } } + +extension ButtonIconViewController: ComponentSampleable { + static func makeSample() -> ComponentSample { + let component = Self.makeComponent() + component.iconName = .addToFavorite + component.size = .large + return ComponentSample(component: component, trailingPinningType: .lessThanOrEqual) + } +} diff --git a/VDSSample/ViewControllers/ButtonViewController.swift b/VDSSample/ViewControllers/ButtonViewController.swift index e6ba980..d43f9ec 100644 --- a/VDSSample/ViewControllers/ButtonViewController.swift +++ b/VDSSample/ViewControllers/ButtonViewController.swift @@ -49,7 +49,7 @@ class ButtonViewController: BaseViewController