Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/SignalUI/UIKitExtensions/UIStackView+SignalUI.swift
1 views
//
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import UIKit

public extension NSDirectionalEdgeInsets {

    static var buttonContainerLayoutMargins: NSDirectionalEdgeInsets {
        return .init(top: 0, leading: 12, bottom: 16, trailing: 12)
    }
}

public extension UIStackView {

    func addArrangedSubviews(_ subviews: [UIView]) {
        for subview in subviews {
            addArrangedSubview(subview)
        }
    }

    func removeArrangedSubviewsAfter(_ subview: UIView) {
        guard let subviewIndex = arrangedSubviews.firstIndex(of: subview) else { return }

        let viewsToRemove = arrangedSubviews.suffix(from: subviewIndex.advanced(by: 1))
        for view in viewsToRemove {
            removeArrangedSubview(view)
        }
    }

    func addHairline(with color: UIColor) {
        insertHairline(with: color, at: arrangedSubviews.count)
    }

    func insertHairline(with color: UIColor, at index: Int) {
        let hairlineView = UIView()
        hairlineView.backgroundColor = color
        hairlineView.autoSetDimension(.height, toSize: 1)
        insertArrangedSubview(hairlineView, at: index)
    }

    func addBackgroundView(_ backgroundView: UIView) {
        addSubview(backgroundView)
        sendSubviewToBack(backgroundView)
        backgroundView.autoPinEdgesToSuperviewEdges()
    }

    @discardableResult
    func addBackgroundView(withBackgroundColor backgroundColor: UIColor, cornerRadius: CGFloat = 0) -> UIView {
        let backgroundView = UIView(frame: bounds)
        backgroundView.backgroundColor = backgroundColor
        backgroundView.layer.cornerRadius = cornerRadius
        self.addBackgroundView(backgroundView)
        return backgroundView
    }

    /// Adds a `UIVisualEffectView` with a `UIBlurEffect` as a background to the view.
    /// - Parameters:
    ///   - blur: The blur effect style to use.
    ///   - accessibilityFallbackColor: An optional fallback color when the
    ///   system "Reduce Transparency" accessibility feature is enabled. If this
    ///   is not set, the system automatically calculates a color to use.
    ///   - cornerRadius: The corner radius the background view should have.
    /// - Returns: The background subview which has been added and pinned to the superview.
    @discardableResult
    func addBackgroundBlurView(
        blur: UIBlurEffect.Style,
        accessibilityFallbackColor: UIColor? = nil,
        cornerRadius: CGFloat = 0,
    ) -> UIView {
        if
            UIAccessibility.isReduceTransparencyEnabled,
            let accessibilityFallbackColor
        {
            return self.addBackgroundView(
                withBackgroundColor: accessibilityFallbackColor,
                cornerRadius: cornerRadius,
            )
        } else {
            let blurEffectView = UIVisualEffectView(effect: UIBlurEffect(style: blur))
            self.addBackgroundView(blurEffectView)
            return blurEffectView
        }
    }

    @discardableResult
    func addBorderView(withColor color: UIColor, strokeWidth: CGFloat, cornerRadius: CGFloat = 0) -> UIView {
        let borderView = UIView(frame: bounds)
        borderView.isUserInteractionEnabled = false
        borderView.backgroundColor = .clear
        borderView.isOpaque = false
        borderView.layer.borderColor = color.cgColor
        borderView.layer.borderWidth = strokeWidth
        borderView.layer.cornerRadius = cornerRadius
        addSubview(borderView)
        borderView.autoPinEdgesToSuperviewEdges()
        return borderView
    }

    @discardableResult
    func addPillBackgroundView(backgroundColor: UIColor) -> UIView {
        let backgroundView = OWSLayerView.pillView()
        backgroundView.backgroundColor = backgroundColor
        self.addBackgroundView(backgroundView)
        return backgroundView
    }

    class func verticalButtonStack(buttons: [UIButton], isFullWidthButtons: Bool = true) -> UIStackView {
        let stackView = UIStackView(arrangedSubviews: buttons)
        stackView.axis = .vertical
        stackView.spacing = 12
        stackView.alignment = isFullWidthButtons ? .fill : .center
        stackView.isLayoutMarginsRelativeArrangement = true
        stackView.directionalLayoutMargins = .buttonContainerLayoutMargins
        return stackView
    }
}

public extension UIView {

    /// A Boolean value that determines whether the view is hidden while working
    /// around a UIStackView bug where hidden subviews sometimes re-appear.
    var isHiddenInStackView: Bool {
        get { isHidden }
        set {
            // Setting isHidden to true when already hidden can cause layout issues
            if isHidden != newValue {
                isHidden = newValue
            }
            alpha = newValue ? 0 : 1
        }
    }
}