-
Notifications
You must be signed in to change notification settings - Fork 0
/
UIView+Extensions.swift
547 lines (468 loc) · 18 KB
/
UIView+Extensions.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
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
//
// UIView+Extensions.swift
// OYExtensions
//
// Created by osmanyildirim
//
import UIKit
extension UIView {
/// Initializer method
static public func oy_init() -> UIView {
UIView()
}
/// Initializer method with `frame` value
public static func oy_init(frame: CGRect) -> UIView {
UIView(frame: frame)
}
/// Initializer method with `x, y, width, height` values
static public func oy_init(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat) -> UIView {
UIView(frame: .oy_init(x, y, width, height))
}
/// Initializer method with `touchUpInside action`
/// Usage:
///
/// `let button: UIButton = UIButton.oy_init { view in
/// // do stuff
/// `}
public static func oy_init<T: OYInit>(touchUpInside: ((_ view: T) -> Void)?) -> T {
let view = T.oy_init()
let gesture = OYTapGesture {
touchUpInside?(view as! T)
}
view.addGestureRecognizer(gesture)
return view as! T
}
/// Init with `Nib` with `frame and bundle` values
public static func oy_initWithNib<T: OYInit>(frame: CGRect, bundle: Bundle = .main) -> T {
let nib = UINib(nibName: oy_reuseIdentifier, bundle: bundle)
guard let view = nib.instantiate(withOwner: self, options: nil).first as? Self else {
fatalError("\(#function) Cannot instantiate view controller of type \(oy_reuseIdentifier)")
}
view.frame = frame
return view as! T
}
/// Hide UIView with animation
public func oy_hide() {
isHidden = true
}
/// Determines whether user events are ignored and removed from the event queue
public func oy_isEnableOrDisable(isEnable: Bool) {
isUserInteractionEnabled = isEnable
}
/// Remove UIView with animation
public func oy_remove(animated: Bool = false) {
UIView.animate(withDuration: animated ? 0.5 : 0.0) {
self.alpha = 0
} completion: { _ in
self.removeFromSuperview()
}
}
/// All subviews of UIView
var oy_allSubviews: [UIView] {
oy_subviews(self)
}
/// UIView's all subviews of a given type
public func oy_allSubviews<T>(of type: T.Type) -> [T] {
var views = [T]()
for subview in subviews {
if let view = subview as? T {
views.append(view)
} else if !subview.subviews.isEmpty {
views.append(contentsOf: subview.oy_allSubviews(of: T.self))
}
}
return views
}
/// Add array of subviews to UIView
public func oy_addSubviews(_ subviews: UIView...) {
_ = subviews.map({ addSubview($0) })
}
/// Insert of subview with index
public func oy_insertSubview(_ subview: UIView, index: Int) {
insertSubview(subview, at: index)
}
/// Remove subviews of UIView
public func oy_removeSubviews() {
_ = subviews.map({ $0.removeFromSuperview() })
}
/// Remove all subviews of UIView
public func oy_removeAllSubviews() {
while subviews.count > 0 {
subviews.last?.removeFromSuperview()
}
}
/// Copy UIView
public func oy_copy<T>() throws -> T? {
let data = try NSKeyedArchiver.archivedData(withRootObject: self, requiringSecureCoding: false)
return try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? T
}
/// Snapshot of UIView
@objc public var oy_snapshot: UIImage? {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
layer.render(in: rendererContext.cgContext)
}
}
/// Get and Set background color of UIView
public var oy_backgroundColor: UIColor? {
get { backgroundColor }
set(value) { backgroundColor = value }
}
/// Circle UIView
public func oy_circle() {
layer.cornerRadius = frame.height / 2
layer.masksToBounds = true
}
/// Set corner radius of view
public func oy_cornerRadius(corners: CACornerMask = [.layerMinXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner], radius: CGFloat) {
layer.cornerRadius = radius
layer.masksToBounds = true
layer.maskedCorners = corners
}
/// Add border to UIView
public func oy_addBorder(color: UIColor, width: CGFloat, radius: CGFloat? = nil) {
layer.borderWidth = width
layer.borderColor = color.cgColor
layer.masksToBounds = true
guard let radius else { return }
layer.cornerRadius = radius
}
/// Remove UIView's border
public func oy_removeBorder(maskToBounds: Bool = true) {
layer.borderWidth = 0
layer.cornerRadius = 0
layer.borderColor = nil
layer.masksToBounds = maskToBounds
}
/// Update UIView's border with animation
public func oy_updateBorderAnimate(color: UIColor?, width: CGFloat?, radius: CGFloat? = nil, duration: CGFloat = 0.5) {
UIViewPropertyAnimator(duration: duration, curve: .easeIn) {
if let color {
self.layer.borderColor = color.cgColor
}
if let width {
self.layer.borderWidth = width
}
if let radius {
self.layer.cornerRadius = radius
}
}.startAnimation()
}
/// Add dashed border to UIView
public func oy_dashedBorder(color: UIColor, width: CGFloat, radius: CGFloat) {
let shapeLayer = CAShapeLayer()
let shapeRect = CGRect(x: 0, y: 0, width: frame.width, height: frame.height)
shapeLayer.bounds = shapeRect
shapeLayer.position = CGPoint(x: frame.width / 2, y: frame.height / 2)
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = color.cgColor
shapeLayer.lineWidth = width
shapeLayer.lineJoin = CAShapeLayerLineJoin.round
shapeLayer.lineDashPattern = [7, 7]
shapeLayer.path = UIBezierPath(roundedRect: shapeRect, cornerRadius: radius).cgPath
layer.addSublayer(shapeLayer)
}
/// Get and Set cornerRadius for UIView
public var oy_cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = newValue > 0
}
}
/// Get and Set borderWidth for UIView
public var oy_borderWidth: CGFloat {
get {
return layer.borderWidth
}
set {
layer.borderWidth = newValue
}
}
/// Get and Set borderColor for UIView
public var oy_borderColor: UIColor? {
get {
if let borderColor = layer.borderColor {
return UIColor(cgColor: borderColor)
} else {
return nil
}
}
set {
layer.borderColor = newValue?.cgColor
}
}
/// Global point on UIWindow of UIView
public var oy_globalPoint: CGPoint? {
guard let window = UIApplication.oy_keyWindow else {
return nil
}
return window.convert(frame.origin, to: nil)
}
/// Global frame on UIWindow of UIView
public var oy_globalFrame: CGRect? {
guard let window = UIApplication.oy_keyWindow else {
return nil
}
return convert(bounds, to: window)
}
/// Impact between UI elements
public func oy_impact(_ style: UIImpactFeedbackGenerator.FeedbackStyle = .light) {
let feedbackGenerator = UIImpactFeedbackGenerator(style: style)
feedbackGenerator.impactOccurred()
}
/// Add UITapGestureRecognizer to UIView with completion handler
public func oy_didTap(tapCount: Int = 1, touchCount: Int = 1, _ completion: ((UIView) -> Void)?) {
let gesture = OYTapGesture(tapCount: tapCount, touchCount: touchCount) {
completion?(self)
}
addGestureRecognizer(gesture)
}
/// Add UILongPressGestureRecognizer to UIView with completion handler
public func oy_didLongPress(_ closure: ((UIView) -> Void)?) {
let gesture = OYLongPressGesture {
closure?(self)
}
addGestureRecognizer(gesture)
}
/// Add UISwipeGestureRecognizer to UIView with completion handler
public func oy_didSwipe(direction: UISwipeGestureRecognizer.Direction, touchCount: Int = 1, _ completion: ((UIView) -> Void)?) {
let gesture = OYSwipeGesture(direction: direction, touchCount: touchCount) {
completion?(self)
}
addGestureRecognizer(gesture)
}
/// Add UIPanGestureRecognizer to UIView with completion handler
public func oy_didPan(_ completion: ((UIView) -> Void)?) {
let gesture = OYPanGesture {
completion?(self)
}
addGestureRecognizer(gesture)
}
/// Add UIPinchGestureRecognizer to UIView with completion handler
public func oy_didPinch(_ completion: ((UIView) -> Void)?) {
let gesture = OYPinchGesture {
completion?(self)
}
addGestureRecognizer(gesture)
}
/// Set shadow to UIView
public func oy_setShadow(color: UIColor, opacity: Float = 0.5, offSet: CGSize, radius: CGFloat = 1, scale: Bool = true) {
layer.masksToBounds = false
layer.shadowColor = color.cgColor
layer.shadowOpacity = opacity
layer.shadowOffset = offSet
layer.shadowRadius = radius
layer.shadowPath = UIBezierPath(rect: self.bounds).cgPath
layer.shouldRasterize = true
layer.rasterizationScale = scale ? UIScreen.main.scale : 1
}
/// Remove shadow of UIView
public func oy_removeShadow(maskToBounds: Bool = true) {
layer.shadowColor = nil
layer.shadowOpacity = 0
layer.shadowOffset = CGSize(width: 0, height: 0)
layer.shadowRadius = 0
layer.cornerRadius = 0
layer.shadowPath = nil
layer.masksToBounds = maskToBounds
}
/// `view.oy_addGradient(colors: [.yellow, .red], direction: .leftBottomToRightTop)`
public func oy_addGradient(colors: [UIColor], direction: AnimationHelper.GradientDirection) {
let gradient = CAGradientLayer()
gradient.frame = bounds
var gradientColors: [Any] = colors
for index in 0 ..< colors.count {
let currentColor: UIColor = colors[index]
gradientColors[index] = currentColor.cgColor
}
gradient.colors = gradientColors
switch direction {
case .vertical:
gradient.startPoint = CGPoint(x: 0.5, y: 0.0)
gradient.endPoint = CGPoint(x: 0.5, y: 1.0)
case .horizontal:
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
case .leftTopToRightBottom:
gradient.startPoint = CGPoint(x: 0.0, y: 0.0)
gradient.endPoint = CGPoint(x: 1.0, y: 1.0)
case .leftBottomToRightTop:
gradient.startPoint = CGPoint(x: 0.0, y: 1.0)
gradient.endPoint = CGPoint(x: 1.0, y: 0.0)
case .rightTopToLeftBottom:
gradient.startPoint = CGPoint(x: 1.0, y: 0.0)
gradient.endPoint = CGPoint(x: 0.0, y: 1.0)
case .rightBottomToLeftTop:
gradient.startPoint = CGPoint(x: 1.0, y: 1.0)
gradient.endPoint = CGPoint(x: 0.0, y: 0.0)
case let .custom(startPoint, endPoint):
gradient.startPoint = startPoint
gradient.endPoint = endPoint
}
layer.insertSublayer(gradient, at: 0)
}
/// Make clear hole with corner radius on UIView
public func oy_makeHole(frame: CGRect, cornerRadius: CGFloat) {
let maskLayer = CAShapeLayer()
maskLayer.fillRule = .evenOdd
maskLayer.fillColor = UIColor.black.cgColor
let pathToOverlay = UIBezierPath(rect: bounds)
pathToOverlay.append(UIBezierPath(roundedRect: frame, cornerRadius: cornerRadius))
pathToOverlay.usesEvenOddFillRule = true
maskLayer.path = pathToOverlay.cgPath
layer.mask = maskLayer
}
// MARK: - Animations
/// Start `FadeIn` animation with duration
public func oy_fadeIn(duration: TimeInterval = 0.5, skipSetAlphaZero: Bool = false) {
if !skipSetAlphaZero {
alpha = 0
}
UIView.animate(withDuration: duration) {
self.alpha = 1
}
}
/// Start `FadeOut` animation with duration
public func oy_fadeOut(duration: TimeInterval = 0.5) {
UIView.animate(withDuration: duration) {
self.alpha = 0
}
}
/// Start `Rotate` animation
public func oy_rotateAnimation(duration: TimeInterval) {
guard layer.animation(forKey: AnimationHelper.rotateKey) == nil else { return }
let animation = CABasicAnimation(keyPath: "transform.rotation")
animation.fromValue = 0.0
animation.toValue = Float.pi * 2.0
animation.duration = duration
animation.repeatCount = Float.infinity
layer.add(animation, forKey: AnimationHelper.rotateKey)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + duration) {
self.oy_stopRotateAnimation()
}
}
/// Stop `Rotate` animation
public func oy_stopRotateAnimation() {
oy_stopAnimation(for: AnimationHelper.rotateKey)
}
/// Start `Shake` animation
public func oy_shakeAnimation(duration: TimeInterval, repeatCount: Int) {
guard layer.animation(forKey: AnimationHelper.shakeKey) == nil else { return }
let animation = CAKeyframeAnimation(keyPath: "transform")
animation.values = [NSValue(caTransform3D: CATransform3DMakeTranslation(-5, 0, 0)), NSValue(caTransform3D: CATransform3DMakeTranslation(5, 0, 0))
]
animation.autoreverses = true
animation.repeatCount = Float(repeatCount)
animation.duration = duration
layer.add(animation, forKey: AnimationHelper.shakeKey)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + duration) {
self.oy_stopShakeAnimation()
}
}
/// Stop `Shake` animation
public func oy_stopShakeAnimation() {
oy_stopAnimation(for: AnimationHelper.shakeKey)
}
/// Start `Flash` animation
public func oy_flashAnimation(duration: TimeInterval, repeatCount: Float) {
guard layer.animation(forKey: AnimationHelper.opacityKey) == nil else { return }
let animation = CABasicAnimation(keyPath: "opacity")
animation.duration = duration
animation.fromValue = 0
animation.toValue = 1
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
animation.autoreverses = true
animation.repeatCount = repeatCount
layer.add(animation, forKey: AnimationHelper.opacityKey)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + duration) {
self.oy_stopFlashAnimation()
}
}
/// Stop `Flash` animation
public func oy_stopFlashAnimation() {
oy_stopAnimation(for: AnimationHelper.opacityKey)
}
/// Start `Heart` animation
public func oy_heartAnimation(duration: TimeInterval) {
let pulse = CASpringAnimation(keyPath: "transform.scale")
pulse.duration = duration
pulse.fromValue = 1.0
pulse.toValue = 1.2
pulse.autoreverses = true
pulse.repeatCount = .infinity
pulse.initialVelocity = 0.5
pulse.damping = 0.8
layer.add(pulse, forKey: AnimationHelper.heartKey)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + duration) {
self.oy_stopHeartAnimation()
}
}
/// Stop `Heart` animation
public func oy_stopHeartAnimation() {
oy_stopAnimation(for: AnimationHelper.heartKey)
}
/// Start `Flip` animation
public func oy_flipAnimation(duration: TimeInterval, direction: AnimationHelper.FlipDirection, autoReverse: Bool = false) {
guard layer.animation(forKey: AnimationHelper.flipKey) == nil else { return }
let animation = CATransition()
animation.subtype = CATransitionSubtype(rawValue: direction.rawValue)
animation.startProgress = 0
animation.endProgress = 1.0
animation.type = CATransitionType(rawValue: "flip")
animation.duration = duration
animation.repeatCount = 1
animation.autoreverses = autoReverse
layer.add(animation, forKey: AnimationHelper.flipKey)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + duration) {
self.oy_stopFlipAnimation()
}
}
/// Stop `Flip` animation
public func oy_stopFlipAnimation() {
oy_stopAnimation(for: AnimationHelper.flipKey)
}
}
extension UIView {
/// Private methods for find all subviews
private func oy_subviews(_ parent: UIView, level: Int = 0) -> [UIView] {
var result = [UIView]()
for subview in parent.subviews {
result.append(subview)
if subview.subviews.isEmpty { continue }
result += oy_subviews(subview, level: level + 1)
}
return result
}
}
// MARK: - Animations Constants
extension UIView {
public struct AnimationHelper {
static let flipKey = "oy_flip_animation_key"
static let heartKey = "oy_heart_animation_key"
static let opacityKey = "oy_opacity_animation_key"
static let rotateKey = "oy_rotate_animation_key"
static let shakeKey = "oy_shake_animation_key"
public enum GradientDirection {
case horizontal
case vertical
case leftBottomToRightTop
case leftTopToRightBottom
case rightBottomToLeftTop
case rightTopToLeftBottom
case custom(startPoint: CGPoint, endPoint: CGPoint)
}
public enum FlipDirection: String {
case fromTop
case fromBottom
case fromLeft
case fromRight
}
}
private func oy_stopAnimation(for key: String) {
guard layer.animation(forKey: key) != nil else { return }
layer.removeAnimation(forKey: key)
}
}