jsdw_ios/QuickLocation/UIKit/HUD/DLHUD.swift

536 lines
18 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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))%"
}
}