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
}
}