Created
March 25, 2025 00:24
-
-
Save zats/0f09e5bcd7b6ee26008e96b76e057605 to your computer and use it in GitHub Desktop.
Recreating iOS Screenshot Flash Animation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import QuartzCore | |
import UIKit | |
class _SSSFlashSuperColorView: UIView { | |
private var style: Int = 0 | |
override init(frame: CGRect) { | |
super.init(frame: frame) | |
_updateBackgroundColor() | |
} | |
required init?(coder: NSCoder) { | |
super.init(coder: coder) | |
_updateBackgroundColor() | |
} | |
func setStyle(_ style: Int) { | |
if self.style != style { | |
self.style = style | |
_updateBackgroundColor() | |
} | |
} | |
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { | |
super.traitCollectionDidChange(previousTraitCollection) | |
let previousStyle = previousTraitCollection?.userInterfaceStyle | |
let currentStyle = traitCollection.userInterfaceStyle | |
if previousStyle != currentStyle { | |
_updateBackgroundColor() | |
} | |
} | |
private func _updateBackgroundColor() { | |
let currentStyle = traitCollection.userInterfaceStyle | |
switch style { | |
case 2: | |
backgroundColor = UIColor(white: currentStyle == .dark ? 0.2 : 1.1, alpha: 1.0) | |
case 0: | |
backgroundColor = UIColor(white: currentStyle == .dark ? 0.2 : 1.1, alpha: 1.0) | |
default: | |
fatalError("Not implemented") | |
// let filterType = currentStyle == .dark ? kCAFilterPlusL : kCAFilterPlusD | |
// let filter = CAFilter(type: filterType) | |
// backgroundColor = UIColor(red: 2.0, green: 2.0, blue: 2.0, alpha: 1.0) | |
// layer.compositingFilter = filter | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <Foundation/Foundation.h> | |
#import <UIKit/UIKit.h> | |
NS_ASSUME_NONNULL_BEGIN | |
@interface CABackdropLayer: CALayer | |
@end | |
@interface CAFilter : NSObject <NSCopying, NSMutableCopying> | |
- (nullable instancetype)initWithType:(nonnull NSString *)type; | |
@property (copy) NSString *name; | |
@end | |
@interface UIView () | |
- (BOOL) _shouldAnimatePropertyWithKey:(NSString *)arg1; | |
@end | |
extern NSString *const kCAFilterGaussianBlur; | |
NS_ASSUME_NONNULL_END |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
class SSBlurringFlashView: SSFlashView { | |
private let blurView = SSBlurView() | |
private let superColorView = _SSSFlashSuperColorView() | |
private var completionBlock: (() -> Void)? | |
override init(frame: CGRect) { | |
super.init(frame: frame) | |
setupView() | |
} | |
required init?(coder: NSCoder) { | |
super.init(coder: coder) | |
setupView() | |
} | |
private func setupView() { | |
addSubview(blurView) | |
addSubview(superColorView) | |
blurView.isHidden = true | |
superColorView.isHidden = true | |
setNeedsLayout() | |
} | |
override func layoutSubviews() { | |
super.layoutSubviews() | |
blurView.frame = bounds | |
superColorView.frame = bounds | |
} | |
override func flash(completion: (() -> Void)?) { | |
completionBlock?() | |
completionBlock = completion | |
blurView.isHidden = false | |
superColorView.isHidden = false | |
let duration = SSBlurringFlashView.expectedAnimationDuration(forStyle: style) | |
let blurAnimator = UIViewPropertyAnimator(duration: duration, controlPoint1: CGPoint(x: 0.25, y: 0.1), controlPoint2: CGPoint(x: 0.25, y: 1.0)) { | |
self.superColorView.alpha = 0 | |
} | |
let blurRadiusAnimator = UIViewPropertyAnimator(duration: duration, controlPoint1: CGPoint(x: 0.25, y: 0.1), controlPoint2: CGPoint(x: 0.25, y: 1.0)) { | |
self.blurView.setBlurRadius(0) | |
} | |
blurRadiusAnimator.addCompletion { _ in self.blurViewRadiusAnimatorCompleted() } | |
blurAnimator.addCompletion { _ in self.superColorViewBackgroundColorAnimatorCompleted() } | |
blurAnimator.startAnimation() | |
blurRadiusAnimator.startAnimation() | |
} | |
private func blurViewRadiusAnimatorCompleted() { | |
runCompletionBlockIfAppropriate() | |
} | |
private func superColorViewBackgroundColorAnimatorCompleted() { | |
runCompletionBlockIfAppropriate() | |
} | |
private func runCompletionBlockIfAppropriate() { | |
if completionBlock != nil { | |
completionBlock?() | |
completionBlock = nil | |
} | |
} | |
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { | |
return nil | |
} | |
class override func expectedAnimationDuration(forStyle style: Int) -> TimeInterval { | |
return style == 2 ? 1.0 : 0.0 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
class SSBlurView: UIView { | |
override class var layerClass: AnyClass { | |
return CABackdropLayer.self | |
} | |
override init(frame: CGRect) { | |
super.init(frame: frame) | |
setupBlur() | |
} | |
required init?(coder: NSCoder) { | |
super.init(coder: coder) | |
setupBlur() | |
} | |
private func setupBlur() { | |
guard let blurFilter = CAFilter(type: kCAFilterGaussianBlur) else { return } | |
blurFilter.setValue(true, forKey: "inputHardEdges") | |
blurFilter.setValue(true, forKey: "inputNormalizeEdges") | |
blurFilter.setValue(0.3, forKey: "inputRadius") | |
blurFilter.setValue("low", forKey: "inputQuality") | |
blurFilter.setValue("low", forKey: "inputIntermediateBitDepth") | |
blurFilter.name = "gaussianBlur" | |
let backdropLayer = layer as! CABackdropLayer | |
backdropLayer.filters = [blurFilter] | |
setScale(1.0) | |
} | |
func setScale(_ scale: CGFloat) { | |
(layer as? CABackdropLayer)?.setValue(scale, forKey: "scale") | |
} | |
func scale() -> CGFloat { | |
return (layer as? CABackdropLayer)?.value(forKey: "scale") as? CGFloat ?? 1.0 | |
} | |
func setBlurRadius(_ radius: CGFloat) { | |
(layer as? CABackdropLayer)?.setValue(radius, forKeyPath: "filters.gaussianBlur.inputRadius") | |
} | |
func blurRadius() -> CGFloat { | |
return (layer as? CABackdropLayer)?.value(forKeyPath: "filters.gaussianBlur.inputRadius") as? CGFloat ?? 0 | |
} | |
override func _shouldAnimateProperty(withKey key: String!) -> Bool { | |
if super._shouldAnimateProperty(withKey: key) { | |
return true | |
} | |
return key == "filters.gaussianBlur.inputRadius" || key == "scale" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
class SSFlashView: UIView { | |
var style: Int = 0 | |
static func flashView(forStyle style: Int) -> SSFlashView { | |
let flashViewClass = UIAccessibility.isReduceTransparencyEnabled ? SSReduceTransparencyFlashView.self : SSBlurringFlashView.self | |
let flashView = flashViewClass.init() | |
flashView.setStyle(style) | |
return flashView | |
} | |
required init?(coder: NSCoder) { | |
super.init(coder: coder) | |
commonInit() | |
} | |
override init(frame: CGRect) { | |
super.init(frame: frame) | |
commonInit() | |
} | |
private func commonInit() { | |
self.isUserInteractionEnabled = false | |
} | |
func setStyle(_ style: Int) { | |
self.style = style | |
setNeedsLayout() | |
} | |
func flash(completion: (() -> Void)?) { | |
completion?() | |
} | |
class func expectedAnimationDuration(forStyle style: Int) -> TimeInterval { | |
return (UIAccessibility.isReduceTransparencyEnabled ? SSReduceTransparencyFlashView.self : SSBlurringFlashView.self).expectedAnimationDuration(forStyle: style) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
class SSReduceTransparencyFlashView: SSFlashView { | |
private let colorView = UIView() | |
override init(frame: CGRect) { | |
super.init(frame: frame) | |
setupView() | |
} | |
required init?(coder: NSCoder) { | |
super.init(coder: coder) | |
setupView() | |
} | |
private func setupView() { | |
addSubview(colorView) | |
} | |
override func layoutSubviews() { | |
super.layoutSubviews() | |
colorView.frame = bounds | |
colorView.backgroundColor = style == 0 ? UIColor.white : UIColor.black | |
} | |
override func flash(completion: (() -> Void)?) { | |
let oldCompletionBlock = completion | |
UIView.animate(withDuration: Self.expectedAnimationDuration(forStyle: style), animations: { | |
self.colorView.alpha = 0 | |
}, completion: { _ in | |
oldCompletionBlock?() | |
}) | |
} | |
static override func expectedAnimationDuration(forStyle style: Int) -> TimeInterval { | |
return 1.0 | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Recreating iOS system screenshot flash animation. Here is how to use it: