536 lines
18 KiB
Swift
536 lines
18 KiB
Swift
//
|
||
// 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))%"
|
||
}
|
||
}
|