Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions Example/AccessibilitySnapshot.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
66E2CD14CD63946657E17B15 /* Pods_SnapshotTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A3192D7B9B16BD10FB517A2 /* Pods_SnapshotTests.framework */; };
83A295842AC22D9D00DFBE4F /* UserInputLabelsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83A295832AC22D9D00DFBE4F /* UserInputLabelsViewController.swift */; };
83A295862AC22EEE00DFBE4F /* UserInputLabelsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83A295852AC22EEE00DFBE4F /* UserInputLabelsTests.swift */; };
AAAA00012242E7C700AAAA02 /* SemanticContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAAA00012242E7C700AAAA01 /* SemanticContainerViewController.swift */; };
AC3C4C782E39432300D16410 /* UIAccessibilityCustomRotorParsingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4C772E39431800D16410 /* UIAccessibilityCustomRotorParsingTests.swift */; };
AC3C4C7A2E3B7A0C00D16410 /* AccessibilityCustomRotorsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4C792E3B7A0C00D16410 /* AccessibilityCustomRotorsViewController.swift */; };
AC59D4702D83326F0096B803 /* ElementFrameComparisonController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC59D46F2D83326F0096B803 /* ElementFrameComparisonController.swift */; };
Expand Down Expand Up @@ -156,6 +157,7 @@
83A295832AC22D9D00DFBE4F /* UserInputLabelsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserInputLabelsViewController.swift; sourceTree = "<group>"; };
83A295852AC22EEE00DFBE4F /* UserInputLabelsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserInputLabelsTests.swift; sourceTree = "<group>"; };
88C33CBF672C290CE1EE86AF /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
AAAA00012242E7C700AAAA01 /* SemanticContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SemanticContainerViewController.swift; sourceTree = "<group>"; };
AC3C4C772E39431800D16410 /* UIAccessibilityCustomRotorParsingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIAccessibilityCustomRotorParsingTests.swift; sourceTree = "<group>"; };
AC3C4C792E3B7A0C00D16410 /* AccessibilityCustomRotorsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityCustomRotorsViewController.swift; sourceTree = "<group>"; };
AC59D46F2D83326F0096B803 /* ElementFrameComparisonController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementFrameComparisonController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -225,6 +227,7 @@
3D39BFB12239BEA5009C3EF4 /* ActivationPointViewController.swift */,
3DA12A3122405B9E00EB3C33 /* DataTableViewController.swift */,
3DBAC28E2242E7C700EF4D0A /* ListContainerViewController.swift */,
AAAA00012242E7C700AAAA01 /* SemanticContainerViewController.swift */,
3DBAC2902242F9B200EF4D0A /* LandmarkContainerViewController.swift */,
3D3F2E132263E6B900F7608E /* InvertColorsViewController.swift */,
3DDE7FF524C6D6BF00999ABA /* AccessibilityCustomActionsViewController.swift */,
Expand Down Expand Up @@ -466,6 +469,7 @@
};
607FACCF1AFB9204008FA782 = {
CreatedOnToolsVersion = 6.3.1;
DevelopmentTeam = EYF346PHUG;
LastSwiftMigration = 0900;
};
607FACE41AFB9204008FA782 = {
Expand Down Expand Up @@ -672,6 +676,7 @@
3DDE7FF624C6D6BF00999ABA /* AccessibilityCustomActionsViewController.swift in Sources */,
AC3C4C7A2E3B7A0C00D16410 /* AccessibilityCustomRotorsViewController.swift in Sources */,
3DBAC28F2242E7C700EF4D0A /* ListContainerViewController.swift in Sources */,
AAAA00012242E7C700AAAA02 /* SemanticContainerViewController.swift in Sources */,
AC725B842B06D07E009AD59B /* AccessibilityCustomContentViewController.swift in Sources */,
AC5C5FE22C627DA300E1C4E7 /* NavBarBackButtonAccessibilityTraitsViewController.swift in Sources */,
3DBEAA5B2222953E00FAE61D /* SwitchControlViewController.swift in Sources */,
Expand Down Expand Up @@ -903,7 +908,7 @@
baseConfigurationReference = ED63B7AD78B189E8940B6C80 /* Pods-AccessibilitySnapshotDemo.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = EYF346PHUG;
INFOPLIST_FILE = AccessibilitySnapshot/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
Expand All @@ -921,7 +926,7 @@
baseConfigurationReference = CCFF2A604706B71DC0CBD38B /* Pods-AccessibilitySnapshotDemo.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = EYF346PHUG;
INFOPLIST_FILE = AccessibilitySnapshot/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
Expand Down
1 change: 1 addition & 0 deletions Example/AccessibilitySnapshot/RootViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ final class RootViewController: UITableViewController {
)
}),
("List Container", { _ in return ListContainerViewController() }),
("Semantic Container", { _ in return SemanticContainerViewController() }),
("Landmark Container", { _ in return LandmarkContainerViewController() }),
("Invert Colors", { _ in return InvertColorsViewController() }),
("User Input Labels", { _ in return UserInputLabelsViewController() }),
Expand Down
177 changes: 177 additions & 0 deletions Example/AccessibilitySnapshot/SemanticContainerViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
//
// Copyright 2019 Square Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Paralayout
import UIKit

final class SemanticContainerViewController: AccessibilityViewController {

// MARK: - UIViewController

override func loadView() {
self.view = View()
}

}

// MARK: -

private extension SemanticContainerViewController {

final class MediaControlsContainer: UIView {

// MARK: - Life Cycle

init(title: String) {
self.titleLabel = UILabel()
self.previousButton = UIButton(type: .system)
self.playPauseButton = UIButton(type: .system)
self.nextButton = UIButton(type: .system)

super.init(frame: .zero)

titleLabel.text = title
titleLabel.font = .preferredFont(forTextStyle: .headline)

let config = UIImage.SymbolConfiguration(pointSize: 32, weight: .medium)

previousButton.setImage(UIImage(systemName: "backward.fill", withConfiguration: config), for: .normal)
previousButton.accessibilityLabel = "Previous"

playPauseButton.setImage(UIImage(systemName: "play.fill", withConfiguration: config), for: .normal)
playPauseButton.accessibilityLabel = "Play"

nextButton.setImage(UIImage(systemName: "forward.fill", withConfiguration: config), for: .normal)
nextButton.accessibilityLabel = "Next"

addSubview(titleLabel)
addSubview(previousButton)
addSubview(playPauseButton)
addSubview(nextButton)

accessibilityLabel = title
accessibilityElements = [previousButton, playPauseButton, nextButton]
}

@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

// MARK: - Private Properties

private let titleLabel: UILabel
private let previousButton: UIButton
private let playPauseButton: UIButton
private let nextButton: UIButton

// MARK: - UIView

override func layoutSubviews() {
super.layoutSubviews()

titleLabel.sizeToFit()
previousButton.sizeToFit()
playPauseButton.sizeToFit()
nextButton.sizeToFit()

titleLabel.frame.origin = CGPoint(x: (bounds.width - titleLabel.bounds.width) / 2, y: 8)

let buttonY = titleLabel.frame.maxY + 16
let totalButtonWidth = previousButton.bounds.width + playPauseButton.bounds.width + nextButton.bounds.width + 40
let startX = (bounds.width - totalButtonWidth) / 2

previousButton.frame.origin = CGPoint(x: startX, y: buttonY)
playPauseButton.frame.origin = CGPoint(x: previousButton.frame.maxX + 20, y: buttonY)
nextButton.frame.origin = CGPoint(x: playPauseButton.frame.maxX + 20, y: buttonY)
}

override func sizeThatFits(_ size: CGSize) -> CGSize {
titleLabel.sizeToFit()
playPauseButton.sizeToFit()
let height = 8 + titleLabel.bounds.height + 16 + playPauseButton.bounds.height + 8
return CGSize(width: size.width, height: height)
}

// MARK: - UIAccessibility

override var accessibilityContainerType: UIAccessibilityContainerType {
get {
return .semanticGroup
}
set {
// No-op.
}
}

}

final class View: UIView {

// MARK: - Life Cycle

override init(frame: CGRect) {
topControls = MediaControlsContainer(title: "Player 1")
bottomControls = MediaControlsContainer(title: "Player 2")

super.init(frame: frame)

addSubview(topControls)
addSubview(bottomControls)

accessibilityElements = [topControls, bottomControls]
}

@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

// MARK: - Private Properties

private let topControls: MediaControlsContainer
private let bottomControls: MediaControlsContainer

// MARK: - UIView

override func layoutSubviews() {
super.layoutSubviews()

let statusBarHeight = window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0

let controlsWidth = bounds.width - 32
let topControlsSize = topControls.sizeThatFits(CGSize(width: controlsWidth, height: .greatestFiniteMagnitude))
let bottomControlsSize = bottomControls.sizeThatFits(CGSize(width: controlsWidth, height: .greatestFiniteMagnitude))

topControls.frame = CGRect(
x: 16,
y: statusBarHeight + 40,
width: controlsWidth,
height: topControlsSize.height
)

bottomControls.frame = CGRect(
x: 16,
y: topControls.frame.maxY + 40,
width: controlsWidth,
height: bottomControlsSize.height
)
}

}

}

6 changes: 6 additions & 0 deletions Example/SnapshotTests/AccessibilityContainersTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,10 @@ final class AccessibilityContainersTests: SnapshotTestCase {
SnapshotVerifyAccessibility(viewController.view)
}

func testSemanticContainer() {
let viewController = SemanticContainerViewController()
viewController.view.frame = UIScreen.main.bounds
SnapshotVerifyAccessibility(viewController.view)
}

}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ public final class AccessibilitySnapshotView: SnapshotAndLegendView {

private var displayMarkers: [DisplayMarker] = []

private var containerOverlayViews: [UIView] = []

// MARK: - Public Methods

/// Parse the `containedView`'s accessibility and add appropriate visual elements to represent it.
Expand All @@ -118,6 +120,10 @@ public final class AccessibilitySnapshotView: SnapshotAndLegendView {
$0.activationPointView?.removeFromSuperview()
}

// Clean up any previous container overlays.
self.containerOverlayViews.forEach { $0.removeFromSuperview() }
self.containerOverlayViews = []

let viewController = containedView.next as? UIViewController
let originalParent = viewController?.parent
let originalSuperviewAndIndex = containedView.superviewWithSubviewIndex()
Expand Down Expand Up @@ -153,11 +159,20 @@ public final class AccessibilitySnapshotView: SnapshotAndLegendView {
containedView.layoutIfNeeded()

let parser = AccessibilityHierarchyParser()
let markers = parser.parseAccessibilityElements(in: containedView, rotorResultLimit: snapshotConfiguration.rotors.resultLimit)

let parsedHierarchy = parser.parseAccessibilityHierarchy(in: containedView, rotorResultLimit: snapshotConfiguration.rotors.resultLimit)

// Add container overlays (dashed rounded rectangles for semantic groups, etc.)
for container in parsedHierarchy.containers {
let containerOverlayView = ContainerOverlayView(
frame: snapshotView.bounds,
container: container
)
snapshotView.addSubview(containerOverlayView)
containerOverlayViews.append(containerOverlayView)
}

var displayMarkers: [DisplayMarker] = []
for (index, marker) in markers.enumerated() {
for (index, marker) in parsedHierarchy.markers.enumerated() {
let color = snapshotConfiguration.markerColors[index % snapshotConfiguration.markerColors.count]

let legendView = LegendView(marker: marker, color: color, configuration: snapshotConfiguration)
Expand Down
27 changes: 27 additions & 0 deletions Sources/AccessibilitySnapshot/Core/OverlayView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,31 @@ internal extension AccessibilitySnapshotView {
fatalError("init(coder:) has not been implemented")
}
}

/// A view that draws a dashed rounded rectangle to represent an accessibility container.
final class ContainerOverlayView: UIView {

init(frame: CGRect, container: AccessibilityContainer) {
super.init(frame: frame)

backgroundColor = .clear

let insetRect = container.frame.insetBy(dx: -4, dy: -4)
let path = UIBezierPath(roundedRect: insetRect, cornerRadius: 12)

let borderLayer = CAShapeLayer()
borderLayer.path = path.cgPath
borderLayer.strokeColor = UIColor.darkGray.cgColor
borderLayer.fillColor = UIColor.clear.cgColor
borderLayer.lineWidth = 2
borderLayer.lineDashPattern = [6, 4] as [NSNumber]
borderLayer.lineCap = .round

layer.addSublayer(borderLayer)
}

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