Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: square/Listable
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 14.0.3
Choose a base ref
...
head repository: square/Listable
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 14.1.0
Choose a head ref
  • 2 commits
  • 26 files changed
  • 3 contributors

Commits on Mar 28, 2024

  1. reorder control now proxies accessibility into a seperate element (#533)

    This allows the gesture recognizer to be applied to a view without
    overriding its accessibility.
    Useful in situations where you'd like the gesture to apply to the entire
    cell for example.
    RoyalPineapple authored Mar 28, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    c3f8240 View commit details

Commits on Mar 29, 2024

  1. Bumping versions to 14.1.0 (#535)

    ### Fixed
    - `ListReorderGesture` no longer blocks child accessibility, now
    exposing a proxy element for accessible control.
    
    ### Checklist
    
    Please do the following before merging:
    
    - [X] Ensure any public-facing changes are reflected in the
    [changelog](https://github.com/kyleve/Listable/blob/main/CHANGELOG.md).
    Include them in the `Main` section.
    
    ---------
    
    Co-authored-by: Meher Kasam <[email protected]>
    meherkasam-square and meherkasam authored Mar 29, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    6d7cfe9 View commit details
Showing with 6,551 additions and 5,992 deletions.
  1. +78 −18 BlueprintUILists/Sources/ListReorderGesture.swift
  2. +13 −7 CHANGELOG.md
  3. +64 −4 Demo/Sources/Demos/Demo Screens/CollectionViewAppearance.swift
  4. +18 −0 Demo/Sources/Demos/Demo Screens/ReorderingViewController.swift
  5. +15 −0 ListableUI/Sources/Item/ItemReordering.swift
  6. +1 −1 ListableUI/Sources/ListableLocalizedStrings.swift
  7. +6 −6 Podfile.lock
  8. +5 −4 docs/BlueprintLists/Extensions/Element.html
  9. +9 −9 docs/BlueprintLists/Structs/ListReorderGesture.html
  10. +5 −4 ...BlueprintLists/docsets/BlueprintLists.docset/Contents/Resources/Documents/Extensions/Element.html
  11. +9 −9 ...tLists/docsets/BlueprintLists.docset/Contents/Resources/Documents/Structs/ListReorderGesture.html
  12. +1 −1 docs/BlueprintLists/docsets/BlueprintLists.docset/Contents/Resources/Documents/search.json
  13. BIN docs/BlueprintLists/docsets/BlueprintLists.docset/Contents/Resources/docSet.dsidx
  14. BIN docs/BlueprintLists/docsets/BlueprintLists.tgz
  15. +1 −1 docs/BlueprintLists/search.json
  16. +1 −1 docs/BlueprintLists/undocumented.json
  17. +833 −553 docs/JSON/BlueprintUILists.json
  18. +5,436 −5,370 docs/JSON/ListableUI.json
  19. +26 −0 docs/Listable/Structs/ItemReordering/GestureRecognizer.html
  20. +26 −0 ...ocsets/Listable.docset/Contents/Resources/Documents/Structs/ItemReordering/GestureRecognizer.html
  21. +1 −1 docs/Listable/docsets/Listable.docset/Contents/Resources/Documents/search.json
  22. BIN docs/Listable/docsets/Listable.docset/Contents/Resources/docSet.dsidx
  23. BIN docs/Listable/docsets/Listable.tgz
  24. +1 −1 docs/Listable/search.json
  25. +1 −1 docs/Listable/undocumented.json
  26. +1 −1 version.rb
96 changes: 78 additions & 18 deletions BlueprintUILists/Sources/ListReorderGesture.swift
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
// Created by Kyle Van Essen on 11/14/19.
//

import Accessibility
import BlueprintUI
import ListableUI
import UIKit
@@ -38,6 +39,7 @@ import UIKit
/// ```
public struct ListReorderGesture : Element
{

public enum Begins {
case onTap
case onLongPress
@@ -54,9 +56,8 @@ public struct ListReorderGesture : Element

let actions : ReorderingActions

/// The acccessibility Label of the item that will be reordered.
/// This will be set as the gesture's accessibilityValue to provide a richer VoiceOver utterance.
public var reorderItemAccessibilityLabel : String? = nil
/// The acccessibility label for the reorder element. Defaults to "Reorder".
public var accessibilityLabel : String?

/// Creates a new re-order gesture which wraps the provided element.
///
@@ -66,6 +67,7 @@ public struct ListReorderGesture : Element
isEnabled : Bool = true,
actions : ReorderingActions,
begins: Begins = .onTap,
accessibilityLabel: String? = nil,
wrapping element : Element
) {
self.isEnabled = isEnabled
@@ -74,6 +76,8 @@ public struct ListReorderGesture : Element

self.begins = begins

self.accessibilityLabel = accessibilityLabel

self.element = element
}

@@ -88,24 +92,16 @@ public struct ListReorderGesture : Element
public func backingViewDescription(with context: ViewDescriptionContext) -> ViewDescription?
{
return ViewDescription(View.self) { config in

config.builder = {
View(frame: context.bounds, wrapping: self)
}
config.contentView = { $0.containerView }

config.apply { view in
view.isAccessibilityElement = true
view.accessibilityLabel = ListableLocalizedStrings.ReorderGesture.accessibilityLabel
view.accessibilityValue = reorderItemAccessibilityLabel
view.accessibilityHint = ListableLocalizedStrings.ReorderGesture.accessibilityHint
view.accessibilityTraits.formUnion(.button)
view.accessibilityCustomActions = accessibilityActions()

view.recognizer.isEnabled = self.isEnabled

view.recognizer.apply(actions: self.actions)

view.recognizer.minimumPressDuration = begins == .onLongPress ? 0.5 : 0.0
view.apply(self)
}

}
}

@@ -118,9 +114,14 @@ public extension Element
func listReorderGesture(
with actions : ReorderingActions,
isEnabled : Bool = true,
begins: ListReorderGesture.Begins = .onTap
begins: ListReorderGesture.Begins = .onTap,
accessibilityLabel: String? = nil
) -> Element {
ListReorderGesture(isEnabled: isEnabled, actions: actions, begins: begins, wrapping: self)
ListReorderGesture(isEnabled: isEnabled,
actions: actions,
begins: begins,
accessibilityLabel: accessibilityLabel,
wrapping: self)
}
}

@@ -129,25 +130,84 @@ fileprivate extension ListReorderGesture
{
private final class View : UIView
{

let containerView = UIView()
let recognizer : ItemReordering.GestureRecognizer
private lazy var proxyElement = UIAccessibilityElement(accessibilityContainer: self)
private var minimumPressDuration: TimeInterval = 0.0 {
didSet {
updateGesturePressDuration()
}
}

@objc private func updateGesturePressDuration() {
self.recognizer.minimumPressDuration = UIAccessibility.isVoiceOverRunning ? 0.0 : self.minimumPressDuration
}

init(frame: CGRect, wrapping : ListReorderGesture)
{
self.recognizer = .init()

super.init(frame: frame)

recognizer.accessibilityProxy = proxyElement
NotificationCenter.default.addObserver(self, selector: #selector(updateGesturePressDuration) , name: UIAccessibility.voiceOverStatusDidChangeNotification, object: nil)

self.isOpaque = false
self.clipsToBounds = false
self.backgroundColor = .clear

self.addGestureRecognizer(self.recognizer)

self.isAccessibilityElement = false

containerView.isOpaque = false
containerView.backgroundColor = .clear
addSubview(containerView)
}

@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
listableInternalFatal()
}

func apply(_ model: ListReorderGesture) {
proxyElement.accessibilityLabel = model.accessibilityLabel ?? ListableLocalizedStrings.ReorderGesture.accessibilityLabel
proxyElement.accessibilityHint = ListableLocalizedStrings.ReorderGesture.accessibilityHint
proxyElement.accessibilityTraits.formUnion(.button)
proxyElement.accessibilityCustomActions = model.accessibilityActions()

recognizer.isEnabled = model.isEnabled

recognizer.apply(actions: model.actions)
minimumPressDuration = model.begins == .onLongPress ? 0.5 : 0.0
}

override func layoutSubviews() {
super.layoutSubviews()
containerView.frame = bounds
}

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if UIAccessibility.isVoiceOverRunning,
UIAccessibility.focusedElement(using: .notificationVoiceOver) as? NSObject == proxyElement {
// Intercept touch events to avoid activating contained elements.
return self
}

return super.hitTest(point, with: event)
}

override var accessibilityElements: [Any]? {
get {
guard recognizer.isEnabled else { return super.accessibilityElements }
proxyElement.accessibilityFrame = self.accessibilityFrame
proxyElement.accessibilityActivationPoint = self.accessibilityActivationPoint
return [containerView, proxyElement]
}
set {
fatalError("Cannot set accessibility elements directly")
}
}
}
}

20 changes: 13 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -14,6 +14,11 @@

# Past Releases

# [14.1.0] – 2024-03-28

### Fixed
- `ListReorderGesture` no longer blocks child accessibility, now exposing a proxy element for accessible control.

# [14.0.3] – 2024-03-03

### Fixed
@@ -1028,13 +1033,14 @@ listActions.scrolling.scrollToSection(
Earlier releases were ad-hoc and not tracked. To see all changes, please reference [closed PRs on Github](https://github.com/kyleve/Listable/pulls?q=is%3Apr+is%3Aclosed).


[Main]: https://github.com/square/Listable/compare/14.0.3...main
[14.0.3]: https://github.com/square/Listable/square/14.0.2...14.0.3
[14.0.2]: https://github.com/square/Listable/square/14.0.1...14.0.2
[14.0.1]: https://github.com/square/Listable/square/14.0.0...14.0.1
[14.0.0]: https://github.com/square/Listable/square/13.1.0...14.0.0
[13.1.0]: https://github.com/square/Listable/square/13.0.0...13.1.0
[13.0.0]: https://github.com/square/Listable/square/12.0.0...13.0.0
[Main]: https://github.com/square/Listable/compare/14.1.0...main
[14.1.0]: https://github.com/square/Listable/compare/14.0.3...14.1.0
[14.0.3]: https://github.com/square/Listable/compare/14.0.2...14.0.3
[14.0.2]: https://github.com/square/Listable/compare/14.0.1...14.0.2
[14.0.1]: https://github.com/square/Listable/compare/14.0.0...14.0.1
[14.0.0]: https://github.com/square/Listable/compare/13.1.0...14.0.0
[13.1.0]: https://github.com/square/Listable/compare/13.0.0...13.1.0
[13.0.0]: https://github.com/square/Listable/compare/12.0.0...13.0.0
[12.0.0]: https://github.com/square/Listable/compare/11.0.0...12.0.0
[11.0.0]: https://github.com/square/Listable/compare/10.3.1...11.0.0
[10.3.1]: https://github.com/square/Listable/compare/10.3.0...10.3.1
68 changes: 64 additions & 4 deletions Demo/Sources/Demos/Demo Screens/CollectionViewAppearance.swift
Original file line number Diff line number Diff line change
@@ -119,6 +119,65 @@ struct DemoHeader2 : BlueprintHeaderFooterContent, Equatable
}


struct DemoTile : BlueprintItemContent, Equatable, LocalizedCollatableItemContent
{
var text : String
var secondaryText: String

var identifierValue: String {
return "\(text) \(secondaryText)"
}

func element(with info : ApplyItemContentInfo) -> Element
{
Button(onTap:{
print("\(text) tapped!")
}, wrapping: Row { row in
row.verticalAlignment = .center

row.add(child:
Column { col in
col.add(child: Label(text: text) {
$0.font = .systemFont(ofSize: 17.0, weight: .medium)
$0.color = info.state.isActive ? .white : .darkGray
})
col.add(child: Label(text: secondaryText) {
$0.font = .systemFont(ofSize: 12.0, weight: .light)
$0.color = info.state.isActive ? .white : .gray
})
}
.inset(horizontal: 15.0, vertical: 24.0)
)
})
.accessibilityElement(label: text, value: secondaryText, traits: [.button])
.listReorderGesture(with: info.reorderingActions, begins: .onLongPress, accessibilityLabel: "Reorder \(text)")


}

func backgroundElement(with info: ApplyItemContentInfo) -> Element?
{
Box(
backgroundColor: info.state.isReordering ? .white(0.8) : .white,
cornerStyle: .rounded(radius: 8.0)
)
}

func selectedBackgroundElement(with info: ApplyItemContentInfo) -> Element?
{
Box(
backgroundColor: .white(0.2),
cornerStyle: .rounded(radius: 8.0),
shadowStyle: .simple(radius: 2.0, opacity: 0.15, offset: .init(width: 0.0, height: 1.0), color: .black)
)
}

var collationString: String {
return "\(text) \(secondaryText)"
}
}


struct DemoItem : BlueprintItemContent, Equatable, LocalizedCollatableItemContent
{
var text : String
@@ -142,10 +201,11 @@ struct DemoItem : BlueprintItemContent, Equatable, LocalizedCollatableItemConten

if info.isReorderable {
row.addFixed(
child: Image(
image: UIImage(named: "ReorderControl"),
contentMode: .center
)
child:
Image(
image: UIImage(named: "ReorderControl"),
contentMode: .center
)
.listReorderGesture(with: info.reorderingActions, begins: requiresLongPress ? .onLongPress : .onTap)
)
}
18 changes: 18 additions & 0 deletions Demo/Sources/Demos/Demo Screens/ReorderingViewController.swift
Original file line number Diff line number Diff line change
@@ -108,5 +108,23 @@ final class ReorderingViewController : ListViewController
item.reordering = ItemReordering(sections: .current)
}
}

list += Section("5") { section in
section.header = DemoHeader(title: "Tile Section")
section.layouts.table.columns = .init(count: 2, spacing: 15.0)

section += Item(DemoTile(text: "Item 0", secondaryText: "Section 4")) { item in
item.reordering = ItemReordering(sections: .current)
}
section += Item(DemoTile(text: "Item 1", secondaryText: "Section 4")) { item in
item.reordering = ItemReordering(sections: .current)
}
section += Item(DemoTile(text: "Item 2", secondaryText: "Section 4")) { item in
item.reordering = ItemReordering(sections: .current)
}
section += Item(DemoTile(text: "Item 3", secondaryText: "Section 4")) { item in
item.reordering = ItemReordering(sections: .current)
}
}
}
}
15 changes: 15 additions & 0 deletions ListableUI/Sources/Item/ItemReordering.swift
Original file line number Diff line number Diff line change
@@ -161,12 +161,16 @@ extension ItemReordering {
private var onMove : OnMove? = nil
private var onEnd : OnEnd? = nil

// If this is set the gesture recognizer will only fire when the accessibilityProxy is selected by voiceover.
public var accessibilityProxy: NSObject?

/// Creates a gesture recognizer with the provided target and selector.
public override init(target: Any?, action: Selector?)
{
super.init(target: target, action: action)

self.addTarget(self, action: #selector(updated))

self.minimumPressDuration = 0
}

@@ -206,6 +210,10 @@ extension ItemReordering {

@objc private func updated()
{
guard accessibilityShouldContinue() else {
self.state = .cancelled
return
}
switch self.state {
case .possible: break
case .began:
@@ -228,6 +236,13 @@ extension ItemReordering {
@unknown default: listableInternalFatal()
}
}

private func accessibilityShouldContinue() -> Bool {
guard UIAccessibility.isVoiceOverRunning, let proxy = accessibilityProxy else {
return true
}
return UIAccessibility.focusedElement(using: .notificationVoiceOver) as? NSObject == proxy
}
}
}

2 changes: 1 addition & 1 deletion ListableUI/Sources/ListableLocalizedStrings.swift
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@ public struct ListableLocalizedStrings {
bundle: .listableUIResources,
value: "Reorder",
comment: "Accessibility label for the reorder control on an item")

public static let accessibilityHint = NSLocalizedString("reorder.AccessibilityHint",
tableName: nil,
bundle: .listableUIResources,
Loading