Skip to content

Instantly share code, notes, and snippets.

@zats
Created March 25, 2025 00:24
Show Gist options
  • Save zats/0f09e5bcd7b6ee26008e96b76e057605 to your computer and use it in GitHub Desktop.
Save zats/0f09e5bcd7b6ee26008e96b76e057605 to your computer and use it in GitHub Desktop.
Recreating iOS Screenshot Flash Animation
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
}
}
}
#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
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
}
}
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"
}
}
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)
}
}
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
}
}
@zats
Copy link
Author

zats commented Mar 25, 2025

Recreating iOS system screenshot flash animation. Here is how to use it:

let flashView = SSFlashView.flashView(forStyle: 2)
flashView.frame = view.bounds
self.view.addSubview(flashView)
flashView.flash(completion: nil)

mXd0Dwb8lgzqGFwA

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment