import Foundation import UIKit import Display extension CATransform3D { func interpolate(with other: CATransform3D, fraction: CGFloat) -> CATransform3D { var vectors = Array(repeating: 0.0, count: 16) vectors[0] = self.m11 + (other.m11 - self.m11) * fraction vectors[1] = self.m12 + (other.m12 - self.m12) * fraction vectors[2] = self.m13 + (other.m13 - self.m13) * fraction vectors[3] = self.m14 + (other.m14 - self.m14) * fraction vectors[4] = self.m21 + (other.m21 - self.m21) * fraction vectors[5] = self.m22 + (other.m22 - self.m22) * fraction vectors[6] = self.m23 + (other.m23 - self.m23) * fraction vectors[7] = self.m24 + (other.m24 - self.m24) * fraction vectors[8] = self.m31 + (other.m31 - self.m31) * fraction vectors[9] = self.m32 + (other.m32 - self.m32) * fraction vectors[10] = self.m33 + (other.m33 - self.m33) * fraction vectors[11] = self.m34 + (other.m34 - self.m34) * fraction vectors[12] = self.m41 + (other.m41 - self.m41) * fraction vectors[13] = self.m42 + (other.m42 - self.m42) * fraction vectors[14] = self.m43 + (other.m43 - self.m43) * fraction vectors[15] = self.m44 + (other.m44 - self.m44) * fraction return CATransform3D(m11: vectors[0], m12: vectors[1], m13: vectors[2], m14: vectors[3], m21: vectors[4], m22: vectors[5], m23: vectors[6], m24: vectors[7], m31: vectors[8], m32: vectors[9], m33: vectors[10], m34: vectors[11], m41: vectors[12], m42: vectors[13], m43: vectors[14], m44: vectors[15]) } } private extension CGFloat { func interpolate(with other: CGFloat, fraction: CGFloat) -> CGFloat { let invT = 1.0 - fraction let result = other * fraction + self * invT return result } } private extension CGPoint { func interpolate(with other: CGPoint, fraction: CGFloat) -> CGPoint { return CGPoint(x: self.x.interpolate(with: other.x, fraction: fraction), y: self.y.interpolate(with: other.y, fraction: fraction)) } } private extension CGSize { func interpolate(with other: CGSize, fraction: CGFloat) -> CGSize { return CGSize(width: self.width.interpolate(with: other.width, fraction: fraction), height: self.height.interpolate(with: other.height, fraction: fraction)) } } extension CGRect { func interpolate(with other: CGRect, fraction: CGFloat) -> CGRect { return CGRect(origin: self.origin.interpolate(with: other.origin, fraction: fraction), size: self.size.interpolate(with: other.size, fraction: fraction)) } } private let maxInteritemSpacing: CGFloat = 240.0 let additionalInsetTop: CGFloat = 16.0 private let additionalInsetBottom: CGFloat = 0.0 private let zOffset: CGFloat = -60.0 private let perspectiveCorrection: CGFloat = -1.0 / 1000.0 private let maxRotationAngle: CGFloat = -CGFloat.pi / 2.2 func angle(for origin: CGFloat, itemCount: Int, scrollBounds: CGRect, contentHeight: CGFloat?, insets: UIEdgeInsets) -> CGFloat { var rotationAngle = rotationAngleAt0(itemCount: itemCount) var contentOffset = scrollBounds.origin.y if contentOffset < 0.0 { contentOffset *= 2.0 } var yOnScreen = origin - contentOffset - additionalInsetTop - insets.top if yOnScreen < 0 { yOnScreen = 0 } else if yOnScreen > scrollBounds.height { yOnScreen = scrollBounds.height } let maxRotationVariance = maxRotationAngle - rotationAngleAt0(itemCount: itemCount) rotationAngle += (maxRotationVariance / scrollBounds.height) * yOnScreen return rotationAngle } func final3dTransform(for origin: CGFloat, size: CGSize, contentHeight: CGFloat?, itemCount: Int, forcedAngle: CGFloat? = nil, additionalAngle: CGFloat? = nil, scrollBounds: CGRect, insets: UIEdgeInsets) -> CATransform3D { var transform = CATransform3DIdentity transform.m34 = perspectiveCorrection let rotationAngle = forcedAngle ?? angle(for: origin, itemCount: itemCount, scrollBounds: scrollBounds, contentHeight: contentHeight, insets: insets) var effectiveRotationAngle = rotationAngle if let additionalAngle = additionalAngle { effectiveRotationAngle += additionalAngle } let r = size.height / 2.0 + abs(zOffset / sin(rotationAngle)) let zTranslation = r * sin(rotationAngle) let yTranslation: CGFloat = r * (1 - cos(rotationAngle)) let zTranslateTransform = CATransform3DTranslate(transform, 0.0, -yTranslation, zTranslation) let rotateTransform = CATransform3DRotate(zTranslateTransform, effectiveRotationAngle, 1.0, 0.0, 0.0) return rotateTransform } func interitemSpacing(itemCount: Int, boundingSize: CGSize, insets: UIEdgeInsets) -> CGFloat { var interitemSpacing = maxInteritemSpacing if itemCount > 0 { interitemSpacing = (boundingSize.height - additionalInsetTop - additionalInsetBottom - insets.top) / CGFloat(min(itemCount, 5)) } return interitemSpacing } func frameForIndex(index: Int, size: CGSize, insets: UIEdgeInsets, itemCount: Int, boundingSize: CGSize) -> CGRect { let spacing = interitemSpacing(itemCount: itemCount, boundingSize: boundingSize, insets: insets) var y = additionalInsetTop + insets.top + spacing * CGFloat(index) if itemCount == 1 { y += 72.0 } let origin = CGPoint(x: insets.left, y: y) return CGRect(origin: origin, size: CGSize(width: size.width - insets.left - insets.right, height: size.height)) } func rotationAngleAt0(itemCount: Int) -> CGFloat { let multiplier: CGFloat = min(CGFloat(itemCount), 5.0) - 1.0 return -CGFloat.pi / 7.0 - CGFloat.pi / 7.0 * multiplier / 4.0 } final class BlurView: UIVisualEffectView { private func setup() { for subview in self.subviews { if subview.description.contains("VisualEffectSubview") { subview.isHidden = true } } if let sublayer = self.layer.sublayers?[0], let filters = sublayer.filters { sublayer.backgroundColor = nil sublayer.isOpaque = false let allowedKeys: [String] = [ "gaussianBlur", "colorSaturate" ] sublayer.filters = filters.filter { filter in guard let filter = filter as? NSObject else { return true } let filterName = String(describing: filter) if !allowedKeys.contains(filterName) { return false } return true } } } override var effect: UIVisualEffect? { get { return super.effect } set { super.effect = newValue self.setup() } } override func didAddSubview(_ subview: UIView) { super.didAddSubview(subview) self.setup() } } let shadowImage: UIImage? = { return generateImage(CGSize(width: 1.0, height: 480.0), rotatedContext: { size, context in let bounds = CGRect(origin: CGPoint(), size: size) context.clear(bounds) let gradientColors = [UIColor.black.withAlphaComponent(0.0).cgColor, UIColor.black.withAlphaComponent(0.55).cgColor, UIColor.black.withAlphaComponent(0.55).cgColor] as CFArray var locations: [CGFloat] = [0.0, 0.65, 1.0] let colorSpace = CGColorSpaceCreateDeviceRGB() let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: bounds.height), options: []) }) }()