forked from sochalewski/UIImageViewAlignedSwift
-
Notifications
You must be signed in to change notification settings - Fork 0
/
UIImageViewAligned.swift
257 lines (215 loc) · 8.15 KB
/
UIImageViewAligned.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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
//
// UIImageViewAligned.swift
// UIImageViewAlignedSwift
//
// MIT License.
//
import UIKit
public struct UIImageViewAlignmentMask: OptionSet {
public let rawValue: Int
public init(rawValue: Int) { self.rawValue = rawValue }
/// The option to align the content to the center.
public static let center = UIImageViewAlignmentMask(rawValue: 0)
/// The option to align the content to the left.
public static let left = UIImageViewAlignmentMask(rawValue: 1 << 0)
/// The option to align the content to the right.
public static let right = UIImageViewAlignmentMask(rawValue: 1 << 1)
/// The option to align the content to the top.
public static let top = UIImageViewAlignmentMask(rawValue: 1 << 2)
/// The option to align the content to the bottom.
public static let bottom = UIImageViewAlignmentMask(rawValue: 1 << 3)
/// The option to align the content to the top left.
public static let topLeft: UIImageViewAlignmentMask = [top, left]
/// The option to align the content to the top right.
public static let topRight: UIImageViewAlignmentMask = [top, right]
/// The option to align the content to the bottom left.
public static let bottomLeft: UIImageViewAlignmentMask = [bottom, left]
/// The option to align the content to the bottom right.
public static let bottomRight: UIImageViewAlignmentMask = [bottom, right]
}
@IBDesignable
open class UIImageViewAligned: UIImageView {
/**
The technique to use for aligning the image.
Changes to this property can be animated.
*/
open var alignment: UIImageViewAlignmentMask = .center {
didSet {
guard alignment != oldValue else { return }
updateLayout()
}
}
open override var image: UIImage? {
set {
realImageView?.image = newValue
setNeedsLayout()
}
get {
return realImageView?.image
}
}
open override var highlightedImage: UIImage? {
set {
realImageView?.highlightedImage = newValue
setNeedsLayout()
}
get {
return realImageView?.highlightedImage
}
}
/**
The option to align the content to the top.
It is available in Interface Builder and should not be set programmatically. Use `alignment` property if you want to set alignment outside Interface Builder.
*/
@IBInspectable open var alignTop: Bool {
set {
setInspectableProperty(newValue, alignment: .top)
}
get {
return getInspectableProperty(.top)
}
}
/**
The option to align the content to the left.
It is available in Interface Builder and should not be set programmatically. Use `alignment` property if you want to set alignment outside Interface Builder.
*/
@IBInspectable open var alignLeft: Bool {
set {
setInspectableProperty(newValue, alignment: .left)
}
get {
return getInspectableProperty(.left)
}
}
/**
The option to align the content to the right.
It is available in Interface Builder and should not be set programmatically. Use `alignment` property if you want to set alignment outside Interface Builder.
*/
@IBInspectable open var alignRight: Bool {
set {
setInspectableProperty(newValue, alignment: .right)
}
get {
return getInspectableProperty(.right)
}
}
/**
The option to align the content to the bottom.
It is available in Interface Builder and should not be set programmatically. Use `alignment` property if you want to set alignment outside Interface Builder.
*/
@IBInspectable open var alignBottom: Bool {
set {
setInspectableProperty(newValue, alignment: .bottom)
}
get {
return getInspectableProperty(.bottom)
}
}
open override var isHighlighted: Bool {
set {
super.isHighlighted = newValue
layer.contents = nil
}
get {
return super.isHighlighted
}
}
/**
The inner image view.
It should be used only when necessary.
Accessible to keep compatibility with the original `UIImageViewAligned`.
*/
public private(set) var realImageView: UIImageView?
private var realContentSize: CGSize {
var size = bounds.size
guard let image = image else { return size }
let scaleX = size.width / image.size.width
let scaleY = size.height / image.size.height
switch contentMode {
case .scaleAspectFill:
let scale = max(scaleX, scaleY)
size = CGSize(width: image.size.width * scale, height: image.size.height * scale)
case .scaleAspectFit:
let scale = min(scaleX, scaleY)
size = CGSize(width: image.size.width * scale, height: image.size.height * scale)
case .scaleToFill:
size = CGSize(width: image.size.width * scaleX, height: image.size.height * scaleY)
default:
size = image.size
}
return size
}
public override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
public override init(image: UIImage?) {
super.init(image: image)
setup(image: image)
}
public override init(image: UIImage?, highlightedImage: UIImage?) {
super.init(image: image, highlightedImage: highlightedImage)
setup(image: image, highlightedImage: highlightedImage)
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
open override func layoutSubviews() {
super.layoutSubviews()
layoutIfNeeded()
updateLayout()
}
open override func didMoveToSuperview() {
super.didMoveToSuperview()
layer.contents = nil
}
open override func didMoveToWindow() {
super.didMoveToWindow()
layer.contents = nil
if #available(tvOS 11, iOS 11, *) {
let currentImage = realImageView?.image
image = UIImage()
realImageView?.image = currentImage
}
}
private func setup(image: UIImage? = nil, highlightedImage: UIImage? = nil) {
realImageView = UIImageView(image: image ?? super.image, highlightedImage: highlightedImage ?? super.highlightedImage)
realImageView?.frame = bounds
realImageView?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
realImageView?.contentMode = contentMode
addSubview(realImageView!)
}
private func updateLayout() {
let realSize = realContentSize
var realFrame = CGRect(origin: CGPoint(x: (bounds.size.width - realSize.width) / 2.0,
y: (bounds.size.height - realSize.height) / 2.0),
size: realSize)
if alignment.contains(.left) {
realFrame.origin.x = 0.0
} else if alignment.contains(.right) {
realFrame.origin.x = bounds.maxX - realFrame.size.width
}
if alignment.contains(.top) {
realFrame.origin.y = 0.0
} else if alignment.contains(.bottom) {
realFrame.origin.y = bounds.maxY - realFrame.size.height
}
realImageView?.frame = realFrame.integral
// Make sure we clear the contents of this container layer, since it refreshes from the image property once in a while.
layer.contents = nil
if #available(tvOS 11, iOS 11, *) {
super.image = UIImage()
}
}
private func setInspectableProperty(_ newValue: Bool, alignment: UIImageViewAlignmentMask) {
if newValue {
self.alignment.insert(alignment)
} else {
self.alignment.remove(alignment)
}
}
private func getInspectableProperty(_ alignment: UIImageViewAlignmentMask) -> Bool {
return self.alignment.contains(alignment)
}
}