// // ReliefButton.swift // App // // Created by James Magahern on 8/14/20. // import UIKit class ReliefButton: UIButton { public var constrainedToSquare = true internal var cornerRadius = CGFloat(6.0) { didSet { setNeedsLayout() } } internal let shadowView = UIView(frame: .zero) internal let backgroundView = GradientView(direction: .vertical, colors: ReliefButton.gradientColors(inverted: false, darkMode: false)) static let padding = CGFloat(24.0) static let borderWidth = CGFloat(1.0 / UIScreen.main.scale) override var isHighlighted: Bool { get { super.isHighlighted || remainsPressed } set { super.isHighlighted = newValue setBackgroundInverted(isHighlighted) } } var remainsPressed: Bool = false { didSet { self.isHighlighted = remainsPressed } } init() { super.init(frame: .zero) self.tintColor = .init(dynamicProvider: { traitCollection -> UIColor in if traitCollection.userInterfaceStyle == .light { return .init(white: 0.15, alpha: 1.0) } else { return .white } }) shadowView.alpha = 0.28 shadowView.backgroundColor = UIColor(white: 0.0, alpha: 1.0) shadowView.isUserInteractionEnabled = false shadowView.layer.shadowColor = UIColor.black.cgColor shadowView.layer.shadowOffset = CGSize(width: 0.0, height: 1.0) shadowView.layer.shadowRadius = 1.0 shadowView.layer.shadowOpacity = 1.0 shadowView.layer.masksToBounds = false shadowView.layer.shouldRasterize = true shadowView.layer.rasterizationScale = UIScreen.main.scale shadowView.layer.cornerCurve = .continuous addSubview(shadowView) backgroundView.isUserInteractionEnabled = false backgroundView.layer.masksToBounds = true backgroundView.layer.borderWidth = Self.borderWidth backgroundView.layer.cornerCurve = .continuous backgroundView.layer.shouldRasterize = true backgroundView.layer.rasterizationScale = UIScreen.main.scale addSubview(backgroundView) traitCollectionDidChange(nil) pointerStyleProvider = { (button, pointerEffect, pointerShape) -> UIPointerStyle? in let preview = UITargetedPreview(view: button) return UIPointerStyle(effect: .lift(preview), shape: pointerShape) } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } static func gradientColors(inverted: Bool, darkMode: Bool) -> [UIColor] { if darkMode { if !inverted { return [ UIColor(white: 0.20, alpha: 1.0), UIColor(white: 0.10, alpha: 1.0) ] } else { return [ UIColor(white: 0.10, alpha: 1.0), UIColor(white: 0.30, alpha: 1.0) ] } } else { if !inverted { return [ UIColor(white: 0.98, alpha: 1.0), UIColor(white: 0.93, alpha: 1.0) ] } else { return [ UIColor(white: 0.88, alpha: 1.0), UIColor(white: 0.93, alpha: 1.0) ] } } } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { setBackgroundInverted(isHighlighted) backgroundView.layer.borderColor = { traitCollection -> UIColor in if traitCollection.userInterfaceStyle == .dark { return .init(white: 0.34, alpha: 1.0) } else { return .white } }(traitCollection).cgColor } internal func setBackgroundInverted(_ inverted: Bool) { let darkMode: Bool = (traitCollection.userInterfaceStyle == .dark) backgroundView.colors = Self.gradientColors(inverted: inverted, darkMode: darkMode) } override func imageRect(forContentRect contentRect: CGRect) -> CGRect { let ratio = CGFloat(0.45) let constantSize = round(ratio * contentRect.width) return CGRect( origin: .zero, size: CGSize( // Ensure multiple of two for proper centering. width: constantSize + constantSize.truncatingRemainder(dividingBy: 2), height: constantSize + constantSize.truncatingRemainder(dividingBy: 2) ) ).centered(inRect: contentRect) } override func sizeThatFits(_ size: CGSize) -> CGSize { let ourSize = super.sizeThatFits(size) return CGSize(width: ourSize.width + Self.padding, height: ourSize.height) } override func layoutSubviews() { self.imageView?.contentMode = .scaleAspectFit super.layoutSubviews() sendSubviewToBack(backgroundView) sendSubviewToBack(shadowView) if constrainedToSquare { let backgroundDimension = bounds.height backgroundView.frame = CGRect(origin: .zero, size: CGSize(width: backgroundDimension, height: backgroundDimension)) backgroundView.frame = backgroundView.frame.centeredX(inRect: bounds) } else { backgroundView.frame = bounds } shadowView.frame = backgroundView.frame let shadowPath = UIBezierPath(roundedRect: shadowView.bounds, cornerRadius: cornerRadius) shadowView.layer.shadowPath = shadowPath.cgPath backgroundView.layer.cornerRadius = cornerRadius shadowView.layer.cornerRadius = cornerRadius } }