AI生成Flex布局

AI生成Flex布局

920 发布: 2026/7/26 08:06 本文总阅读量

Flex应用-AI篇

调用方法

// 通话弹窗-余额不足
HNCallAlert.showIncomingCallInsufficientBalance(
    currentBalance: "8云币",
    requiredBalance: "≥30云币",
    recharge: {
        // 去充值
        debug.log("recharge")
    },
    later: {
        // 稍后再说
        debug.log("later")
    }
)
// 通话弹窗-不足一分钟
HNCallAlert.showInCallLowBalance(
    currentBalance: "8云币",
    callPrice: "30云币/分钟",
    recharge: {
        // 充值
        debug.log("recharge2")
    },
    later: {
        // 稍后再说
        debug.log("later2")
    }
)

工具类

//
//  HNCallAlert.swift
//  iyz
//

import FlexLayout
import PinLayout
import UIKit

/// 通话余额相关弹窗的统一入口。
@objcMembers
final class HNCallAlert: NSObject {
    typealias Action = () -> Void

    private weak static var currentAlert: HNCallAlertView?

    /// 主播来电时余额不足,无法接听。
    static func showIncomingCallInsufficientBalance(
        currentBalance: String,
        requiredBalance: String,
        recharge: Action? = nil,
        later: Action? = nil
    ) {
        present(
            configuration: HNCallAlertConfiguration(
                title: "主播邀请您通话",
                message: "您的余额不足,无法接听\n请先充值再接听通话",
                secondItemTitle: "需要余额",
                currentBalance: currentBalance,
                secondItemValue: requiredBalance,
                confirmTitle: "去充值",
                imageName: "call_invite"
            ),
            recharge: recharge,
            later: later
        )
    }

    /// 通话中余额不足一分钟时长。
    static func showInCallLowBalance(
        currentBalance: String,
        callPrice: String,
        recharge: Action? = nil,
        later: Action? = nil
    ) {
        present(
            configuration: HNCallAlertConfiguration(
                title: "余额不足",
                message: "您的余额不足1分钟通话时长\n建议立即充值避免中断",
                secondItemTitle: "通话单价",
                currentBalance: currentBalance,
                secondItemValue: callPrice,
                confirmTitle: "充值",
                imageName: "call_balance"
            ),
            recharge: recharge,
            later: later
        )
    }

    /// 关闭当前通话弹窗,不触发回调。
    static func dismiss() {
        DispatchQueue.main.async {
            currentAlert?.dismiss()
        }
    }

    private static func present(
        configuration: HNCallAlertConfiguration,
        recharge: Action?,
        later: Action?
    ) {
        DispatchQueue.main.async {
            guard let window = UIApplication.shared.kWindow else { return }

            currentAlert?.removeFromSuperview()
            let alert = HNCallAlertView(
                configuration: configuration,
                recharge: recharge,
                later: later
            )
            alert.frame = window.bounds
            alert.autoresizingMask = [.flexibleWidth, .flexibleHeight]
            window.addSubview(alert)
            window.bringSubviewToFront(alert)
            currentAlert = alert
            alert.show()
        }
    }
}

private struct HNCallAlertConfiguration {
    let title: String
    let message: String
    let secondItemTitle: String
    let currentBalance: String
    let secondItemValue: String
    let confirmTitle: String
    let imageName: String
}

private final class HNCallAlertView: BaseFlexView {
    private let configuration: HNCallAlertConfiguration
    private var rechargeAction: HNCallAlert.Action?
    private var laterAction: HNCallAlert.Action?

    private let shadeView = UIView()
    private let panelView = HNCallAlertGradientView()
    private let imageView = UIImageView()
    private let titleLabel = UILabel()
    private let messageLabel = UILabel()
    private let balanceView = UIView()
    private let currentRow = UIView()
    private let secondRow = UIView()
    private let currentTitleLabel = UILabel()
    private let currentValueLabel = UILabel()
    private let secondTitleLabel = UILabel()
    private let secondValueLabel = UILabel()
    private let rechargeButton = HNCallAlertGradientButton(type: .custom)
    private let laterButton = UIButton(type: .custom)

    init(
        configuration: HNCallAlertConfiguration,
        recharge: HNCallAlert.Action?,
        later: HNCallAlert.Action?
    ) {
        self.configuration = configuration
        rechargeAction = recharge
        laterAction = later
        super.init(.fitContainer)
        configureContent()
    }

    required init?(coder: NSCoder) {
        return nil
    }

    override func initUI() {
        shadeView.backgroundColor = UIColor.hex("#000000", 0.72)
        panelView.layer.cornerRadius = 24
        panelView.layer.masksToBounds = true

        imageView.contentMode = .scaleAspectFit

        configureLabel(titleLabel, size: 18, color: "#333333", bold: true)
        configureLabel(messageLabel, size: 14, color: "#666666")
        messageLabel.numberOfLines = 2
        configureLabel(currentTitleLabel, size: 12, color: "#666666", alignment: .left)
        configureLabel(secondTitleLabel, size: 12, color: "#666666", alignment: .left)
        configureLabel(currentValueLabel, size: 12, color: "#8916F6", medium: true, alignment: .right)
        configureLabel(secondValueLabel, size: 12, color: "#333333", medium: true, alignment: .right)

        balanceView.backgroundColor = UIColor.hex("#FFFFFF", 0.5)
        balanceView.layer.cornerRadius = 10

        rechargeButton.titleLabel?.font = kFont(size: 16, bold: true)
        rechargeButton.setTitleColor(.white, for: .normal)
        rechargeButton.addTarget(self, action: #selector(rechargeTapped), for: .touchUpInside)

        laterButton.titleLabel?.font = kFont(size: 14)
        laterButton.setTitleColor(.hex("#888888"), for: .normal)
        laterButton.addTarget(self, action: #selector(laterTapped), for: .touchUpInside)

        rootView.flex.define { flex in
            flex.addItem(shadeView).position(.absolute).all(0)
            flex.addItem(panelView).position(.absolute)
        }

        panelView.flex.define { flex in
            flex.addItem(imageView).alignSelf(.center)
            flex.addItem(titleLabel).alignSelf(.center)
            flex.addItem(messageLabel).alignSelf(.center)
            flex.addItem(balanceView).alignSelf(.center)
            flex.addItem(rechargeButton).alignSelf(.center)
            flex.addItem(laterButton).alignSelf(.center)
        }

        balanceView.flex.paddingHorizontal(12).paddingVertical(10).define { flex in
            flex.addItem(currentRow).height(18)
            flex.addItem(secondRow).marginTop(7).height(18)
        }

        currentRow.flex.direction(.row).alignItems(.center).define { flex in
            flex.addItem(currentTitleLabel).grow(1)
            flex.addItem(currentValueLabel).grow(1)
        }

        secondRow.flex.direction(.row).alignItems(.center).define { flex in
            flex.addItem(secondTitleLabel).grow(1)
            flex.addItem(secondValueLabel).grow(1)
        }
    }

    private func configureContent() {
        imageView.image = UIImage(named: configuration.imageName)
        titleLabel.text = configuration.title
        messageLabel.text = configuration.message
        currentTitleLabel.text = "当前余额"
        currentValueLabel.text = configuration.currentBalance
        secondTitleLabel.text = configuration.secondItemTitle
        secondValueLabel.text = configuration.secondItemValue
        rechargeButton.setTitle(configuration.confirmTitle, for: .normal)
        laterButton.setTitle("稍后再说", for: .normal)
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        guard bounds.width > 0, bounds.height > 0 else { return }

        let designSize = CGSize(width: 292, height: 359)
        let scale = min(1, (bounds.width - 64) / designSize.width)
        let incoming = configuration.imageName == "call_invite"

        panelView.pin
            .width(designSize.width * scale)
            .height(designSize.height * scale)
            .hCenter()
            .vCenter(-35)

        panelView.flex.paddingTop((incoming ? 10 : 14) * scale)
        imageView.flex
            .width((incoming ? 76 : 68) * scale)
            .height((incoming ? 76 : 68) * scale)
        titleLabel.flex
            .marginTop((incoming ? -3 : 1) * scale)
            .width(252 * scale)
            .height(25 * scale)
        messageLabel.flex.marginTop(12 * scale).width(252 * scale).height(40 * scale)
        balanceView.flex.marginTop(15 * scale).width(235 * scale).height(66 * scale)
        rechargeButton.flex
            .marginTop((incoming ? 16 : 12) * scale)
            .width(255 * scale)
            .height(46 * scale)
        laterButton.flex.marginTop(8 * scale).width(100 * scale).height(40 * scale)

        balanceView.flex.paddingHorizontal(12 * scale).paddingVertical(10 * scale)
        currentRow.flex.height(18 * scale)
        secondRow.flex.marginTop(7 * scale).height(18 * scale)

        panelView.flex.layout(mode: .fitContainer)
        rechargeButton.layer.cornerRadius = rechargeButton.bounds.height * 0.5
    }

    func show() {
        alpha = 0
        panelView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
        UIView.animate(
            withDuration: 0.25,
            delay: 0,
            usingSpringWithDamping: 0.82,
            initialSpringVelocity: 0.4,
            options: [.beginFromCurrentState, .allowUserInteraction]
        ) {
            self.alpha = 1
            self.panelView.transform = .identity
        }
    }

    func dismiss(completion: (() -> Void)? = nil) {
        rechargeAction = nil
        laterAction = nil
        UIView.animate(withDuration: 0.18, animations: {
            self.alpha = 0
            self.panelView.transform = CGAffineTransform(scaleX: 0.92, y: 0.92)
        }, completion: { _ in
            self.removeFromSuperview()
            completion?()
        })
    }

    @objc private func rechargeTapped() {
        finish(with: rechargeAction)
    }

    @objc private func laterTapped() {
        finish(with: laterAction)
    }

    private func finish(with action: HNCallAlert.Action?) {
        rechargeButton.isEnabled = false
        laterButton.isEnabled = false
        dismiss(completion: action)
    }

    private func configureLabel(
        _ label: UILabel,
        size: CGFloat,
        color: String,
        bold: Bool = false,
        medium: Bool = false,
        alignment: NSTextAlignment = .center
    ) {
        label.font = medium ? .systemFont(ofSize: size, weight: .medium) : kFont(size: size, bold: bold)
        label.textColor = .hex(color)
        label.textAlignment = alignment
    }
}

private final class HNCallAlertGradientView: UIView {
    private let gradientLayer = CAGradientLayer()

    override init(frame: CGRect) {
        super.init(frame: frame)
        gradientLayer.colors = [UIColor.hex("#FFE7FF").cgColor, UIColor.white.cgColor]
        gradientLayer.startPoint = CGPoint(x: 0.5, y: 0)
        gradientLayer.endPoint = CGPoint(x: 0.5, y: 1)
        layer.insertSublayer(gradientLayer, at: 0)
    }

    required init?(coder: NSCoder) {
        return nil
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        gradientLayer.frame = bounds
    }
}

private final class HNCallAlertGradientButton: UIButton {
    private let gradientLayer = CAGradientLayer()

    override init(frame: CGRect) {
        super.init(frame: frame)
        gradientLayer.colors = [UIColor.hex("#6F07F4").cgColor, UIColor.hex("#AA07F1").cgColor]
        gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
        gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)
        layer.insertSublayer(gradientLayer, at: 0)
        layer.masksToBounds = true
    }

    required init?(coder: NSCoder) {
        return nil
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        gradientLayer.frame = bounds
        gradientLayer.cornerRadius = bounds.height * 0.5
    }
}