-
Notifications
You must be signed in to change notification settings - Fork 0
/
Element.swift
140 lines (126 loc) · 5.52 KB
/
Element.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
//
// QAElement.swift
// UILint
//
// Created by Quinn McHenry on 5/26/20.
//
import UIKit
public enum Element: Comparable, CustomDebugStringConvertible {
case label(font: UIFont, maxLines: Int, text: String, textColor: UIColor,
measuredTextColor: UIColor?, measuredBackgroundColor: UIColor?, base: Base)
case button(fontName: String?, fontSize: CGFloat?, title: String?, hasImage: Bool, base: Base)
case image(image: UIImage?, imageAccessibilityLabel: String?, base: Base)
case other(base: Base)
public struct Base {
public let className: String
public let windowFrame: CGRect?
public let backgroundColor: UIColor?
public let effectiveBackgroundColor: CGColor?
public let wantsTouches: Bool // like a button
public let consumesTouches: Bool // opaque view that blocks
public let depth: Int
public let level: Int
public let contentScaleFactor: CGFloat
public let contentMode: UIView.ContentMode
public let accessibilityIdentifier: String?
public let tag: Int
init(_ view: UIView, depth: Int, level: Int, context: LintingContext) {
let screenshot = context.screenshot?.crop(to: view.windowFrame, viewSize: context.screenshot?.size)
className = view.className
windowFrame = view.windowFrame
backgroundColor = view.backgroundColor
effectiveBackgroundColor = screenshot?.effectiveBackgroundColor()
let enabledGestureRecognizers = view.gestureRecognizers?.filter { $0.isEnabled }.count ?? 0
wantsTouches = (view is UIControl) || enabledGestureRecognizers > 0
consumesTouches = view.consumesTouches
self.depth = depth
self.level = level
contentScaleFactor = view.contentScaleFactor
contentMode = view.contentMode
accessibilityIdentifier = view.accessibilityIdentifier
tag = view.tag
}
}
public var base: Base {
switch self {
case .label(_, _, _, _, _, _, let base): return base
case .button(_, _, _, _, let base): return base
case .image(_, _, let base): return base
case .other(let base): return base
}
}
var depth: Int {
return base.depth
}
var sortOrder: Int {
switch self {
case .label: return 100
case .button: return 200
case .image: return 300
case .other: return 10000
}
}
var isLabel: Bool { sortOrder == 100 }
var isButton: Bool { sortOrder == 200 }
var isImage: Bool { sortOrder == 300 }
func findings(elements: [Element], context: LintingContext) -> [Finding] {
var results = [Finding]()
let enabledChecks = allChecks.filter { check in
!UILintConfig.shared.excludedChecks.contains { $0 == check }
}
enabledChecks.forEach { check in
if context.shouldLint?(self, check) ?? true {
results += check.init()
.findings(forElement: self, elements: elements, context: context)
} else { print("Skipping check \(check.self) on \(self)") }
}
return results
}
func overlaps(_ element: Element) -> Bool {
guard let windowFrame = base.windowFrame, let overlapWindowFrame = element.base.windowFrame else {
return false
}
return windowFrame.intersects(overlapWindowFrame)
}
public var debugDescription: String {
let descriptions: [String?] = [
base.className,
base.tag != 0 ? "tag:\(base.tag)" : nil,
base.accessibilityIdentifier != nil ? "aid:'\(base.accessibilityIdentifier!)'" : nil
]
return descriptions.compactMap { $0 }.joined(separator: " ")
}
init?(view: UIView, depth: Int, level: Int, context: LintingContext) {
let base = Base(view, depth: depth, level: level, context: context)
if let view = view as? UILabel {
let texture = context.screenshot?.crop(to: view.windowFrame, viewSize: context.screenshot!.size)
let extractor = LabelColorExtractor(screenshot: texture, label: view)
self = Element.label(font: view.font,
maxLines: view.numberOfLines,
text: view.text ?? "",
textColor: view.textColor,
measuredTextColor: extractor?.textColor,
measuredBackgroundColor: extractor?.backgroundColor,
base: base)
} else if let view = view as? UIButton {
let font = view.titleLabel?.font
self = Element.button(fontName: font?.fontName,
fontSize: font?.pointSize,
title: view.titleLabel?.text,
hasImage: view.imageView?.image != nil,
base: base)
} else if let view = view as? UIImageView {
self = Element.image(image: view.image,
imageAccessibilityLabel: view.image?.accessibilityLabel,
base: base)
} else {
self = Element.other(base: base)
}
}
public static func < (lhs: Element, rhs: Element) -> Bool {
return lhs.sortOrder < rhs.sortOrder
}
public static func == (lhs: Element, rhs: Element) -> Bool {
return lhs.sortOrder == rhs.sortOrder
}
}