// // File.swift // JHSDK // // Created by osell on 2023/3/24. // import UIKit // MARK: - DLHUD /// DL通用Loading/Toast @objcMembers public class DLHUD: NSObject { /// 是否显示HUD public static var isShowing: Bool { DLHUDView.shared.alpha == 1 } /// 显示Loading /// - Parameters: /// - text: 提示文本 /// - interaction: 是否可以Loading外区域交互,默认true public static func showLoading(text: String? = nil, inView: UIView? = nil, completion: (() -> Void)? = nil) { DLHUDView.shared.setupHUD(text: text, style: .loading, inView: inView) } /// 显示HUD /// - Parameters: /// - text: 提示文本 /// - interaction: 是否可以Loading外区域交互,默认true public static func showInfo(text: String, interaction: Bool = true, inView: UIView? = nil, completion: (() -> Void)? = nil) { DLHUDView.shared.setupHUD(text: text, style: .info, interaction: interaction, inView: inView) let delay = Double(text.count) * 0.03 + 1.25 DLHUDView.shared.dismissHUD(delay: delay, completion: completion) } /// 成功HUD /// - Parameter text: 提示文本 public static func showSuccess(text: String? = nil, inView: UIView? = nil, completion: (() -> Void)? = nil) { DLHUDView.shared.setupHUD(text: text, style: .success, interaction: true, inView: inView) let text = text ?? "" let delay = Double(text.count) * 0.03 + 1.25 DLHUDView.shared.dismissHUD(delay: delay, completion: completion) } /// 失败HUD /// - Parameter text: 提示文本 public static func showError(text: String? = nil, inView: UIView? = nil, completion: (() -> Void)? = nil) { DLHUDView.shared.setupHUD(text: text, style: .error, interaction: true, inView: inView) let text = text ?? "" let delay = Double(text.count) * 0.03 + 1.25 DLHUDView.shared.dismissHUD(delay: delay, completion: completion) } /// 显示进度HUD /// - Parameters: /// - text: 提示文本 /// - progress: 进度,0 ~ 1.0 public static func showProgress(text: String? = nil, progress: CGFloat, inView: UIView? = nil, completion: (() -> Void)? = nil) { if progress >= 0 && progress <= 1 { DLHUDView.shared.setupHUD(text: text, style: .progress(progress: progress), inView: inView) } if progress >= 1 || progress < 0 { DLHUDView.shared.dismissHUD(completion: completion) } } /// 隐藏HUD public static func dismiss(delay: TimeInterval = 0, completion: (() -> Void)? = nil) { DLHUDView.shared.dismissHUD(delay: delay, completion: completion) } } // MARK: - DLHUDConfig @objcMembers public class DLHUDConfig { /// 单例 public static let shared = DLHUDConfig() /// HUD背景样式 public var backgroundStyle: UIBarStyle = .black /// HUD文字颜色 public var textColor: UIColor = .white /// HUD文字大小 public var textFont: UIFont = .boldSystemFont(ofSize: 13) /// HUD Progress颜色 public var progress: UIColor = .white /// Success图标 public var successImage: UIImage? /// Error图标 public var errorImage: UIImage? private init() {} } // MARK: - DLHUDView fileprivate class DLHUDView: UIView { // HUD样式 enum Style { case info, // 默认 loading, // loading success, // 成功 error, // 错误 progress(progress: CGFloat) // 进度 } /// 单例 static let shared = DLHUDView() var completeBlock: (() -> Void)? /// 提示文字 lazy var textLabel: UILabel = { let label = UILabel() label.textAlignment = .center label.baselineAdjustment = .alignCenters label.numberOfLines = 0 label.font = UIFont.systemFont(ofSize: 15) return label }() /// 图片 lazy var iconImageView: UIImageView = { let view = UIImageView(frame: CGRect(x: 0, y: 0, width: 60, height: 60)) view.contentMode = .scaleAspectFit return view }() /// 进度view lazy var progressView: DLProgressView = { let view = DLProgressView() return view }() /// 动画View lazy var animationView: UIView = { let view = UIView(frame: CGRect(x: 0, y: 0, width: 60, height: 60)) return view }() /// hudToolBar lazy var hudToolBar: UIToolbar = { let view = UIToolbar() view.isTranslucent = true view.clipsToBounds = true view.layer.cornerRadius = 8 view.layer.masksToBounds = true return view }() /// 背景 lazy var backgroundView: UIView = { let view = UIView() return view }() /// 定时器 private var timer: Timer? convenience private init() { self.init(frame: UIScreen.main.bounds) alpha = 0 } override private init(frame: CGRect) { super.init(frame: frame) } required internal init?(coder: NSCoder) { super.init(coder: coder) } } // MARK: - Method private extension DLHUDView { /// 设置HUD /// - Parameters: /// - text: 文字 /// - style: 样式 /// - interaction: 是否可以交互 /// - isAutoHide: 是否自动隐藏 /// - inView: 在View显示 func setupHUD(text: String? = nil, style: DLHUDView.Style, interaction: Bool = false, inView: UIView? = nil) { DispatchQueue.main.async { self.hudToolBar.layer.removeAllAnimations() self.setupBackgoundView(with: interaction, in: inView) self.setupToolBar() self.removeOtherView(with: style) switch style { case .progress(let progress): self.setupTextLabel(with: text) self.setupProgressView(with: progress) case .error, .success: self.setupImageView(with: style) self.setupTextLabel(with: text) case .loading: self.setupLoadingView() self.setupTextLabel(with: text) default: self.setupTextLabel(with: text) } // 设置 self.setupSize(with: style) self.displayHUD() } } /// 显示HUD func displayHUD() { synchronized(self.hudToolBar) { if self.alpha == 0 { self.alpha = 1 self.hudToolBar.alpha = 0 self.hudToolBar.transform = CGAffineTransform(scaleX: 0.3, y: 0.3) UIView.animate(withDuration: 0.15, delay: 0, options: [.allowUserInteraction, .curveEaseIn]) { self.hudToolBar.alpha = 1 self.hudToolBar.transform = CGAffineTransform.identity } } } } /// 隐藏HUD func dismissHUD(delay: TimeInterval = 0, completion: (() -> Void)? = nil) { if timer != nil { timer?.invalidate() timer = nil } self.timer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false) { [weak self] _ in guard let self = self else { return } DispatchQueue.main.async { // synchronized(self.hudToolBar) { if self.alpha == 1 { UIView.animate(withDuration: 0.15, delay: 0, options: [.allowUserInteraction, .curveEaseIn]) { self.hudToolBar.transform = CGAffineTransform(scaleX: 0.3, y: 0.3) self.hudToolBar.alpha = 0 } completion: { _ in self.iconImageView.removeFromSuperview() self.animationView.removeFromSuperview() self.progressView.removeFromSuperview() self.progressView.setProgress(0) self.hudToolBar.transform = CGAffineTransform.identity self.hudToolBar.removeFromSuperview() self.backgroundView.removeFromSuperview() self.textLabel.removeFromSuperview() self.alpha = 0 self.removeFromSuperview() completion?() } } // } } } #if swift(>=4.2) RunLoop.current.add(self.timer!, forMode: .common) #else RunLoop.current.add(self.timer!, forMode: .commonModes) #endif } } // MARK: - Private Method private extension DLHUDView { /// 设置背景view func setupBackgoundView(with interaction: Bool, in superView: UIView? = nil) { guard let superTempView = superView ?? UIApplication.keyWindow else { return } if backgroundView.superview == nil || backgroundView.superview != superTempView { backgroundView.removeFromSuperview() backgroundView.frame = superTempView.bounds superTempView.addSubview(backgroundView) } backgroundView.backgroundColor = .clear backgroundView.isUserInteractionEnabled = !interaction } /// 设置hudToolBar func setupToolBar() { if hudToolBar.superview == nil { hudToolBar.frame = .zero backgroundView.addSubview(hudToolBar) } hudToolBar.barStyle = DLHUDConfig.shared.backgroundStyle hudToolBar.backgroundColor = UIColor.white } /// 设置Text func setupTextLabel(with text: String?) { if textLabel.superview == nil { hudToolBar.addSubview(textLabel) } textLabel.text = text textLabel.textColor = DLHUDConfig.shared.textColor textLabel.font = DLHUDConfig.shared.textFont textLabel.isHidden = !(text?.isEmpty != true) } /// 设置自定义View func setupCustomView(with view: UIView) { if view.superview == nil { hudToolBar.addSubview(view) } } /// 设置Progress /// - Parameter progress: CGFloat func setupProgressView(with progress: CGFloat) { if progressView.superview == nil { progressView.frame = CGRect(x: 0, y: 0, width: 60, height: 60) hudToolBar.addSubview(progressView) } progressView.setProgress(progress) progressView.color = DLHUDConfig.shared.progress } /// 设置图标 func setupImageView(with style: DLHUDView.Style) { if iconImageView.superview == nil { hudToolBar.addSubview(iconImageView) } switch style { case .success: iconImageView.image = DLHUDConfig.shared.successImage ?? UIImage(named: "hud_success") case .error: iconImageView.image = DLHUDConfig.shared.errorImage ?? UIImage(named: "hud_error") default: iconImageView.image = nil } } /// 设置Loading func setupLoadingView() { animationView.subviews.forEach { $0.removeFromSuperview() } if animationView.superview == nil { hudToolBar.addSubview(animationView) } let spinner = UIActivityIndicatorView(style: .whiteLarge) spinner.frame = animationView.bounds spinner.color = .lightGray spinner.hidesWhenStopped = true spinner.startAnimating() animationView.addSubview(spinner) } /// 移除非当前类型view /// - Parameter style: Style func removeOtherView(with style: DLHUDView.Style) { switch style { case .info: animationView.removeFromSuperview() progressView.removeFromSuperview() iconImageView.removeFromSuperview() textLabel.removeFromSuperview() case .loading: progressView.removeFromSuperview() iconImageView.removeFromSuperview() textLabel.removeFromSuperview() case .success, .error: animationView.removeFromSuperview() progressView.removeFromSuperview() textLabel.removeFromSuperview() case .progress: animationView.removeFromSuperview() iconImageView.removeFromSuperview() textLabel.removeFromSuperview() } } /// 设置Hud的size /// - Parameter style: Style func setupSize(with style: DLHUDView.Style) { var hudWidth: CGFloat = 120 var hudHeight: CGFloat = 120 let screenSize = backgroundView.superview?.frame.size ?? UIScreen.main.bounds.size let maxWidth = min(screenSize.width, screenSize.height) - 80 if let text = textLabel.text, !text.isEmpty { var textRect = text.boundingRect(with: CGSize(width: maxWidth, height: screenSize.height * 0.65), options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font : DLHUDConfig.shared.textFont], context: nil) hudWidth = ceil(textRect.size.width) + 40 hudHeight = ceil(textRect.size.height) if hudWidth < 120 { hudWidth = 120 } textRect.origin.x = (hudWidth - textRect.size.width) / 2 switch style { case .info: hudHeight = hudHeight + 40 textRect.origin.y = (hudHeight - textRect.size.height) / 2 default: textRect.origin.y = 90 hudHeight = hudHeight + 110 } textLabel.frame = textRect } hudToolBar.bounds = CGRect(origin: .zero, size: CGSize(width: hudWidth, height: hudHeight)) let centerX = hudWidth / 2 var centerY = hudWidth / 2 if textLabel.text != nil { centerY = 50 } progressView.center = CGPoint(x: centerX, y: centerY) animationView.center = CGPoint(x: centerX, y: centerY) iconImageView.center = CGPoint(x: centerX, y: centerY) hudToolBar.center = CGPoint(x: screenSize.width / 2, y: screenSize.height / 2) } } // MARK: - DLProgressView public class DLProgressView: UIView { public var color: UIColor = .white { didSet { setupLayers() } } private var progress: CGFloat = 0 private var layerCircle = CAShapeLayer() private var layerProgress = CAShapeLayer() private var labelPercentage: UILabel = UILabel() convenience init(_ color: UIColor) { self.init(frame: .zero) self.color = color } required init?(coder: NSCoder) { super.init(coder: coder) } override init(frame: CGRect) { super.init(frame: frame) } public override func draw(_ rect: CGRect) { super.draw(rect) setupLayers() } func setupLayers() { subviews.forEach { $0.removeFromSuperview() } layer.sublayers?.forEach { $0.removeFromSuperlayer() } let width = frame.size.width let height = frame.size.height let center = CGPoint(x: width/2, y: height/2) let radiusCircle = width / 2 let radiusProgress = width / 2 let pathCircle = UIBezierPath(arcCenter: center, radius: radiusCircle, startAngle: -0.5 * .pi, endAngle: 1.5 * .pi, clockwise: true) let pathProgress = UIBezierPath(arcCenter: center, radius: radiusProgress, startAngle: -0.5 * .pi, endAngle: 1.5 * .pi, clockwise: true) layerCircle.path = pathCircle.cgPath layerCircle.fillColor = UIColor.clear.cgColor layerCircle.lineWidth = 3 layerCircle.strokeColor = color.withAlphaComponent(0.2).cgColor layerProgress.path = pathProgress.cgPath layerProgress.fillColor = UIColor.clear.cgColor layerProgress.lineWidth = 4 layerProgress.strokeColor = color.cgColor layerProgress.strokeEnd = 0 layer.addSublayer(layerCircle) layer.addSublayer(layerProgress) labelPercentage.frame = self.bounds labelPercentage.textColor = color labelPercentage.textAlignment = .center addSubview(labelPercentage) } public func setProgress(_ value: CGFloat, duration: TimeInterval = 0.2) { let animation = CABasicAnimation(keyPath: "strokeEnd") animation.duration = duration animation.fromValue = progress animation.toValue = value animation.fillMode = .both animation.isRemovedOnCompletion = false layerProgress.add(animation, forKey: "animation") progress = value labelPercentage.text = "\(Int(value*100))%" } }