Epub阅读器0.0.1

This commit is contained in:
shen
2026-05-21 19:40:51 +08:00
commit daa36d8fe7
559 changed files with 106266 additions and 0 deletions
+110
View File
@@ -0,0 +1,110 @@
# SSAlert
实际开发中经常遇到各种自定义的弹窗的需求,其中编写弹窗动画和遮罩就很繁琐,而且一个一个去写重复的代码,代码会变得更加冗余。
SSAlert封装了动画部分,而且支持自定义动画。可以快速构建弹窗,简单易用,基本可以满足大部分弹窗需求。自带常用自定义弹窗,类型系统的UIAlertView,UIActionSheet。
# 特性
- 支持OC、Swift版本
- 支持自定义弹窗内容,可以根据弹窗内容frame布局和自动布局确定弹窗大小
- 支持动画展示后刷新弹窗的大小
- 支带透明、黑色、无三种背景遮罩
- 自带从上、下、左、右、中弹出动画,动画支持弹簧效果,而且可以设置位置偏移量
- 支持自定义动画,动画隐藏回调
- 支持模态视图弹窗,支持拖拽隐藏
- 自带类似系统的弹窗UIAlertView,UIActionSheet
# 效果图
<img src="https://github.com/namesubai/SSAlert/blob/main/demo.gif" width = 20% height = 20% />
# 使用
## 1.导入代码
### Objective-C项目
1. pod 'SSAlert'
2. #import <SSAlert/SSAlert.h>
### Swift项目
1. pod 'SSAlertSwift'
2. import SSAlertSwift
## 2.SSAlertView用法
### SSAlertView的初始化
SSAlertView的初始化有两种,一种是普通弹窗,一种是模态视图弹窗,两种的区别在于,前者是弹出一个View,后者是弹窗一个UIViewController。当需要点击弹窗上按钮不关闭弹窗跳转界面时候,可以使用模态视图弹窗。
普通弹窗初始化:
```
let customView = UIView()
customView.frame = CGRect(x:0,y:0,width:200,height:200)
let alertView = SSAlertView(customView: customView, onView: navigationController!.view)
```
模态视图弹窗初始化:
```
let customView = UIView()
customView.frame = CGRect(x:0,y:0,width:200,height:200)
let alertView = SSAlertView(customView: customView, fromViewController: self)
```
### SSAlertView的动画设置
遵循 **SSAlertAnimation** 协议的自定义动画都可以设置 **SSAlertView****animation** 属性
**SSAlertView**自带的动画效果:**SSAlertDefaultAnmation**,提供上、下、左、右、中弹出动画等
```
let animation = SSAlertDefaultAnmation(state: .fromCenter)
alertView.animation = animation
```
### SSAlertView的展示和隐藏
```
alertView.show()
alertView.hide()
```
### SSAlertView的大小刷新
```
customView.frame = CGRect(x:0,y:0,width:400,height:400)
alertView.refreshFrame()
```
## 3.自带类型系统UIAlert,UIActionSheet的用法
```
///
let alertView = SSAlertView.alertView(style: .alert, title: "自定义Alert弹窗", message: "自带自定义Alert弹窗,类似系统的UIAlertView", cancelButton: "Cancel", otherButtons: ["OK"], onView: navigationController!.view){ index in
print(index)
}
alertView.show()
///
let alertView = SSAlertView.modalAlertView(style: .actionSheet, title: "自定义ActionSheet弹窗", message: "自带自定义ActionSheet弹窗,类似系统的ActionSheet", cancelButton: "Cancel", otherButtons: ["action1", "action2", "action3", "action4"], fromViewController: self) { index in
print(index)
}
alertView.show()
```
## 4.自定义SSAlertCommonView
```
let action1 = SSAlertAction(style: .actionSheet, title: "自定义Action1") {
print("自定义Action1")
}
action1.titleColor = .red
action1.backgroundColor = .yellow
action1.titleFont = UIFont.systemFont(ofSize: 18)
action1.height = 80
let action2 = SSAlertAction(style: .actionSheet, title: "自定义Action2") {
print("自定义Action2")
}
action2.titleColor = .yellow
action2.backgroundColor = .red
action2.titleFont = UIFont.systemFont(ofSize: 22)
action2.height = 55
let commonView = SSAlertCommonView(title: "自定义SSAlertCommonView", message: "文本文本文本文本文本文本", style: .actionSheet, actions: [action1, action2])
commonView.backgroundColor = .white
let alertView = SSAlertView(customView: commonView, onView: navigationController!.view, animation: SSAlertDefaultAnmation(state: .fromBottom))
alertView.show()
```
更多用法请下载代码,观看[demo](https://github.com/namesubai/SSAlert),
觉得好用,手动点个Star❤️❤️❤️❤️
@@ -0,0 +1,24 @@
//
// SSAlertAnimation.swift
// SSAlert
//
// Created by yangsq on 2021/8/13.
//
import Foundation
public typealias PanProgress = CGFloat
public typealias PanCancelProgress = CGFloat
public typealias DimissIsCancel = Bool
public protocol SSAlertAnimation {
///
func animationDuration() -> TimeInterval
///
func showAnimationOfAnimationView(animationView: SSAlertView, viewSize: CGSize, animated: Bool, completion:(((Bool) -> Void))?)
///
func hideAnimationOfAnimationView(animationView: SSAlertView, viewSize: CGSize, animated: Bool, completion:(((Bool) -> Bool))?)
///
func refreshAnimationOfAnimationView(animationView: SSAlertView, viewSize: CGSize, animated: Bool, completion:(((Bool) -> Void))?)
///
func panToDimissTransilatePoint(point: CGPoint, panViewFrame: CGRect) -> (PanProgress, PanCancelProgress)
}
@@ -0,0 +1,46 @@
//
// SSAlertAnimationController.swift
// SSAlert
//
// Created by yangsq on 2021/8/13.
//
import Foundation
class SSAlertAnimationController: UIViewController {
var isDimiss: Bool = false
var isHideStatusBar = false {
didSet {
setNeedsStatusBarAppearanceUpdate()
}
}
var isShowNavWhenViewWillDisappear = true
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .clear
}
override var prefersStatusBarHidden: Bool {
return isHideStatusBar
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(true, animated: animated)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if !isDimiss, isShowNavWhenViewWillDisappear {
navigationController?.setNavigationBarHidden(false, animated: animated)
}
}
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
super.dismiss(animated: flag, completion: completion)
isDimiss = true
}
}
@@ -0,0 +1,335 @@
//
// SSAlertCommonView.swift
// SSAlert
//
// Created by yangsq on 2021/8/13.
//
import Foundation
public extension SSAlertAction {
enum Style {
case alert
case actionSheet
}
}
public class SSAlertAction: NSObject {
public var title: String
public var titleFont = UIFont.systemFont(ofSize: 16)
public var titleColor: UIColor? = nil
public var height: CGFloat = 55
public var backgroundColor: UIColor? = nil
public private(set) var style: Style
private var addAction:(() -> Void)? = nil
public init(style: Style, title: String, addAction: (() -> Void)? = nil) {
self.style = style
self.title = title
self.addAction = addAction
if style == .alert {
height = 50
}
super.init()
}
func triggerAction() {
if let addAction = addAction {
addAction()
}
}
}
extension SSAlertCommonView {
public static var alertMaxWidth = UIScreen.main.bounds.width * 0.7
public static var actionSheetMaxWidth = UIScreen.main.bounds.width
public static var buttonSpace: CGFloat = 0.5
public static var safeAreaBottomMargin: CGFloat {
if #available(iOS 11.0, *) {
return UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0
}
return 0
}
}
public class SSAlertCommonView: UIView {
public lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 16)
label.numberOfLines = 0
label.textAlignment = .center
return label
}()
public lazy var messageLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 14)
label.numberOfLines = 0
label.textAlignment = .center
return label
}()
public lazy var containerView: UIView = {
let view = UIView()
return view
}()
public lazy var buttonView: UIView = {
let view = UIView()
return view
}()
public var lineColor: UIColor? = nil {
didSet {
lineArray.forEach { lineView in
lineView.backgroundColor = lineColor
}
}
}
public var lineEdgeInsets: UIEdgeInsets = .zero {
didSet {
refreshFrame()
}
}
public private(set) var style: SSAlertAction.Style
public private(set) var actions: [SSAlertAction]
public private(set) var title: String?
public private(set) var message: String?
private var buttonArray: [UIButton] = []
private var lineArray: [UIView] = []
private var hasText = false
public init(title: String? = nil, message: String? = nil, style: SSAlertAction.Style, actions:[SSAlertAction] = []) {
self.title = title
self.message = message
self.style = style
self.actions = actions
super.init(frame: .zero)
makeUI()
}
private func makeUI() {
addSubview(containerView)
if let title = title, !title.isEmpty {
titleLabel.text = title
containerView.addSubview(titleLabel)
hasText = true
}
if let message = message, !message.isEmpty {
messageLabel.text = message
containerView.addSubview(messageLabel)
hasText = true
}
if actions.count > 0 {
addSubview(buttonView)
actions.forEach { action in
let button = UIButton(type: .system)
button.setTitle(action.title, for: .normal)
button.backgroundColor = backgroundColor
if let backgroundColor = action.backgroundColor {
button.backgroundColor = backgroundColor
}
button.titleLabel?.font = action.titleFont
button.setTitleColor(action.titleColor, for: .normal)
button.addTarget(self, action: #selector(buttonAction(button:)), for: .touchUpInside)
buttonView.addSubview(button)
buttonArray.append(button)
var isAddLineView = false
if style == .actionSheet {
if hasText {
isAddLineView = true
} else {
if actions[actions.count - 1] != action {
isAddLineView = true
}
}
} else {
if actions.count > 2 {
if hasText {
isAddLineView = true
} else {
if actions[actions.count - 1] != action {
isAddLineView = true
}
}
} else {
if actions.count == 1 {
if hasText {
isAddLineView = true
}
} else {
if hasText {
isAddLineView = true
} else {
if actions[actions.count - 1] != action {
isAddLineView = true
}
}
}
}
}
if isAddLineView {
let linView = UIView()
linView.backgroundColor = UIColor.lightGray.withAlphaComponent(0.5)
buttonView.addSubview(linView)
lineArray.append(linView)
}
}
}
refreshFrame()
}
private func refreshFrame() {
let space: CGFloat = 15
let viewMaxWidth = style == .alert ? SSAlertCommonView.alertMaxWidth : SSAlertCommonView.actionSheetMaxWidth
var height: CGFloat = 0
let width = viewMaxWidth
let containerWidth = viewMaxWidth - space * 2
var originY: CGFloat = 0
var cotainerOriginY: CGFloat = 0
if titleLabel.superview != nil {
let titleSize = titleLabel.sizeThatFits(CGSize(width: containerWidth, height: CGFloat(MAXFLOAT)))
titleLabel.ss_origin = CGPoint(x: 0, y: cotainerOriginY)
titleLabel.ss_size = CGSize(width: containerWidth, height: titleSize.height)
height += (titleSize.height + space)
cotainerOriginY += (space + titleSize.height)
originY = space
}
if messageLabel.superview != nil {
let messageSize = messageLabel.sizeThatFits(CGSize(width: containerWidth, height: CGFloat(MAXFLOAT)))
messageLabel.ss_origin = CGPoint(x: 0, y: cotainerOriginY)
messageLabel.ss_size = CGSize(width: containerWidth, height: messageSize.height)
height += (messageSize.height + space)
cotainerOriginY += (space + messageSize.height)
originY = space
}
containerView.ss_origin = CGPoint(x: space, y: originY)
containerView.ss_size = CGSize(width: containerWidth, height: height)
originY += height
if !buttonArray.isEmpty {
let buttonSpace: CGFloat = SSAlertCommonView.buttonSpace
var buttonTotalHeight: CGFloat = 0
var buttonTopY: CGFloat = 0
if style == .actionSheet {
for index in 0..<buttonArray.count {
let action = actions[index]
let button = buttonArray[index]
if lineArray.count == buttonArray.count {
let lineView = lineArray[index]
lineView.frame = CGRect(x: lineEdgeInsets.left, y: buttonTopY, width: width - lineEdgeInsets.left - lineEdgeInsets.right, height: buttonSpace)
buttonTopY += buttonSpace
}
if lineArray.count == buttonArray.count - 1 {
if index < lineArray.count {
let lineView = lineArray[index]
lineView.frame = CGRect(x: lineEdgeInsets.left, y: buttonTopY + action.height, width: width - lineEdgeInsets.left - lineEdgeInsets.right, height: buttonSpace)
buttonTopY += buttonSpace
}
}
if index == buttonArray.count - 1 {
button.ss_size = CGSize(width: width, height: action.height + SSAlertCommonView.safeAreaBottomMargin)
button.titleEdgeInsets = UIEdgeInsets(top: -SSAlertCommonView.safeAreaBottomMargin, left: 0, bottom: 0, right: 0)
} else {
button.ss_size = CGSize(width: width, height: action.height)
}
button.ss_origin = CGPoint(x: 0, y: buttonTopY)
buttonTopY += button.ss_h
}
buttonTotalHeight = buttonTopY
}
if style == .alert {
if buttonArray.count > 2 {
buttonArray.forEach { button in
let index = buttonArray.firstIndex(of: button)
let action = actions[index!]
if lineArray.count == buttonArray.count {
let lineView = lineArray[index!]
lineView.frame = CGRect(x: lineEdgeInsets.left, y: buttonTopY, width: width - lineEdgeInsets.left - lineEdgeInsets.right, height: buttonSpace)
buttonTopY += buttonSpace
}
if lineArray.count == buttonArray.count - 1 {
if index! < lineArray.count {
let lineView = lineArray[index!]
lineView.frame = CGRect(x: lineEdgeInsets.left, y: buttonTopY + action.height, width: width - lineEdgeInsets.left - lineEdgeInsets.right, height: buttonSpace)
buttonTopY += buttonSpace
}
}
button.ss_origin = CGPoint(x: 0, y: buttonTopY)
button.ss_size = CGSize(width: width, height: action.height)
buttonTopY += action.height
}
buttonTotalHeight = buttonTopY
} else {
if buttonArray.count == 1 {
let button = buttonArray.first!
let action = actions.first!
if lineArray.count > 0 {
let lineView = lineArray.first!
lineView.frame = CGRect(x: lineEdgeInsets.left, y: buttonTopY, width: width - lineEdgeInsets.left - lineEdgeInsets.right, height: buttonSpace)
buttonTopY += buttonSpace
}
button.ss_origin = CGPoint(x: 0, y: buttonTopY)
button.ss_size = CGSize(width: width, height: action.height)
buttonTotalHeight = buttonTopY
} else {
let button1 = buttonArray.first!
let button2 = buttonArray.last!
let action1 = actions.first!
let action2 = actions.last!
let maxButtonHeight = max(action1.height, action2.height)
if hasText {
let lineView1 = lineArray.first!
lineView1.frame = CGRect(x: 0, y: buttonTopY, width: width, height: buttonSpace)
buttonTopY += buttonSpace
}
button1.ss_origin = CGPoint(x: 0, y: buttonTopY)
button1.ss_size = CGSize(width: width / 2 - buttonSpace / 2, height: maxButtonHeight)
button2.ss_origin = CGPoint(x: button1.frame.maxX + buttonSpace, y: buttonTopY)
button2.ss_size = CGSize(width: width / 2 - buttonSpace / 2, height: maxButtonHeight)
let lineView2 = lineArray.last!
lineView2.frame = CGRect(x: button1.frame.maxX, y: buttonSpace, width: buttonSpace, height: button1.ss_h)
buttonTopY += maxButtonHeight
}
buttonTotalHeight = buttonTopY
}
}
buttonView.ss_origin = CGPoint(x: 0, y: originY)
buttonView.ss_size = CGSize(width: width, height: buttonTotalHeight)
height += buttonTotalHeight + (originY > 0 ? space : 0)
}
self.ss_size = CGSize(width: width, height: height)
}
@objc private func buttonAction(button: UIButton) {
let index = buttonArray.firstIndex(of: button)
let action = actions[index!]
action.triggerAction()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
@@ -0,0 +1,223 @@
//
// SSAlertDefaultAnmation.swift
// SSAlert
//
// Created by yangsq on 2021/8/13.
//
import Foundation
extension SSAlertDefaultAnmation {
public enum State {
case fromTop, fromLeft, fromBottom, fromRight, fromCenter
}
}
open class SSAlertDefaultAnmation: NSObject {
public var state: State
///
public var centerOffset: CGPoint = .zero
///
public var duration: TimeInterval = 0.3
///
public var isSpringShowAnimation: Bool = true
///
public var panDimissProgress: CGFloat = 0.4
public var usingSpringWithDamping: CGFloat = 0.8
public var initialSpringVelocity: CGFloat = 0.5
private weak var animationView: SSAlertView? = nil
public init(state: State) {
self.state = state
super.init()
}
private func animation(animated: Bool, isSpringAnimation: Bool = true, animations: @escaping (() -> Void), completion: ((Bool) -> Void)? = nil) {
if animated {
if isSpringAnimation {
UIView.animate(withDuration: animationDuration(),
delay: 0,
usingSpringWithDamping: usingSpringWithDamping,
initialSpringVelocity: initialSpringVelocity,
options: [],
animations: animations,
completion: completion)
} else {
UIView.animate(withDuration: animationDuration(),
animations: animations,
completion: completion)
}
} else {
animations()
if let completion = completion {
completion(true)
}
}
}
public func setCenterOffset(center offset: CGPoint, duration: TimeInterval = 0.3, animated: Bool = true, completion:((Bool) -> Void)?) {
self.duration = duration
self.animation(animated: animated, animations: {
self.animationView?.transform = CGAffineTransform.init(translationX: offset.x, y: offset.y)
}, completion: completion)
}
}
extension SSAlertDefaultAnmation: SSAlertAnimation {
public func animationDuration() -> TimeInterval {
return duration
}
public func showAnimationOfAnimationView(animationView: SSAlertView, viewSize: CGSize, animated: Bool, completion: (((Bool) -> Void))?) {
guard let animationSuperView = animationView.superview else {
return
}
self.animationView = animationView
animationView.alpha = 1
animationView.ss_size = viewSize
switch state {
case .fromBottom:
animationView.ss_centerX = animationSuperView.ss_w / 2 + centerOffset.x
animationView.ss_y = animationSuperView.ss_h
animation(animated: animated, isSpringAnimation: isSpringShowAnimation, animations: { [weak self] in guard let self = self else { return }
animationView.transform = CGAffineTransform.init(translationX: 0, y: -animationView.ss_h + self.centerOffset.y)
animationView.backgroundMask?.alpha = 1;
}, completion: completion)
case .fromTop:
animationView.ss_centerX = animationSuperView.ss_w / 2 + centerOffset.x
animationView.ss_y = -animationView.ss_h
animation(animated: animated, isSpringAnimation: isSpringShowAnimation, animations: { [weak self] in guard let self = self else { return }
animationView.transform = CGAffineTransform.init(translationX: 0, y: animationView.ss_h + self.centerOffset.y)
animationView.backgroundMask?.alpha = 1;
}, completion: completion)
case .fromLeft:
animationView.ss_centerY = animationSuperView.ss_h / 2 + centerOffset.y
animationView.ss_x = -animationView.ss_w
animation(animated: animated, isSpringAnimation: isSpringShowAnimation, animations: {
animationView.transform = CGAffineTransform.init(translationX: animationView.ss_w + self.centerOffset.x, y: 0)
animationView.backgroundMask?.alpha = 1;
}, completion: completion)
case .fromRight:
animationView.ss_centerY = animationSuperView.ss_h / 2 + centerOffset.y
animationView.ss_x = animationSuperView.ss_w
animation(animated: animated, isSpringAnimation: isSpringShowAnimation, animations: {
animationView.transform = CGAffineTransform.init(translationX: -animationView.ss_w + self.centerOffset.x, y: 0)
animationView.backgroundMask?.alpha = 1;
}, completion: completion)
case .fromCenter:
animationView.ss_centerX = animationSuperView.ss_w / 2 + centerOffset.x
animationView.ss_centerY = animationSuperView.ss_h / 2 + centerOffset.y
animationView.transform = CGAffineTransform.init(scaleX: 1.5, y: 1.5)
animationView.alpha = 0
animation(animated: animated, isSpringAnimation: isSpringShowAnimation, animations: {
animationView.alpha = 1
animationView.transform = CGAffineTransform.identity
animationView.backgroundMask?.alpha = 1;
}, completion: completion)
}
}
public func hideAnimationOfAnimationView(animationView: SSAlertView, viewSize: CGSize, animated: Bool, completion: (((Bool) -> Bool))?) {
self.animationView = animationView
animation(animated: animated, isSpringAnimation: false, animations: {
if self.state == .fromCenter {
animationView.alpha = 0
} else {
animationView.transform = CGAffineTransform.identity
}
animationView.backgroundMask?.alpha = 0
}, completion: {
finished in
var isCancel = false
if let completion = completion {
isCancel = completion(finished)
}
if !isCancel {
animationView.removeFromSuperview()
animationView.backgroundMask?.removeFromSuperview()
self.animationView = nil
}
})
}
public func refreshAnimationOfAnimationView(animationView: SSAlertView, viewSize: CGSize, animated: Bool, completion: (((Bool) -> Void))?) {
guard let animationSuperView = animationView.superview else {
return
}
self.animationView = animationView
animationView.alpha = 1
animationView.ss_size = viewSize
switch state {
case .fromBottom:
animationView.ss_centerX = animationSuperView.ss_w / 2.0 + centerOffset.x
animation(animated: animated, animations: {
animationView.ss_y = animationSuperView.ss_h - viewSize.height + self.centerOffset.y
}, completion: completion)
case .fromTop:
animationView.ss_centerX = animationSuperView.ss_w / 2.0 + centerOffset.x
animation(animated: animated, animations: {
animationView.ss_y = self.centerOffset.y
}, completion: completion)
case .fromLeft:
animationView.ss_centerY = animationSuperView.ss_h / 2.0 + centerOffset.y
animation(animated: animated, animations: {
animationView.ss_x = self.centerOffset.x
}, completion: completion)
case .fromRight:
animationView.ss_centerY = animationSuperView.ss_h / 2.0 + centerOffset.y
animation(animated: animated, animations: {
animationView.ss_x = animationSuperView.ss_w - viewSize.width + self.centerOffset.x
}, completion: completion)
case .fromCenter:
animationView.alpha = 0
animationView.ss_centerX = animationSuperView.ss_w / 2 + centerOffset.x
animationView.ss_centerY = animationSuperView.ss_h / 2 + centerOffset.y
animationView.transform = CGAffineTransform.init(scaleX: 1.5, y: 1.5)
animation(animated: animated, animations: {
animationView.alpha = 1
animationView.transform = CGAffineTransform.identity
}, completion: completion)
}
}
public func panToDimissTransilatePoint(point: CGPoint, panViewFrame: CGRect) -> (PanProgress, PanCancelProgress) {
var progress: CGFloat = 0
switch state {
case .fromTop:
if point.y <= 0 {
progress = abs(point.y / panViewFrame.height)
}
case .fromBottom:
if point.y >= 0 {
progress = abs(point.y / panViewFrame.height)
}
case .fromLeft:
if point.x <= 0 {
progress = abs(point.x / panViewFrame.width)
}
case .fromRight:
if point.x >= 0 {
progress = abs(point.x / panViewFrame.width)
}
case .fromCenter:
progress = abs(point.y / panViewFrame.height)
}
return (progress, panDimissProgress)
}
}
@@ -0,0 +1,222 @@
//
// SSAlertPresentAnimation.swift
// SSAlert
//
// Created by yangsq on 2021/8/13.
//
import Foundation
typealias PanDimissAnimation = (CGPoint, CGRect) -> (PanProgress, PanCancelProgress)
typealias DimissAnimation = (DimissIsCancel) -> Void
class SSCustomInteractiveAnimation: UIPercentDrivenInteractiveTransition {
private(set) var panGestureRecognizer: UIPanGestureRecognizer
private var panDimissAnimation: PanDimissAnimation?
private var dimissAnimation: DimissAnimation?
init(panGestureRecognizer: UIPanGestureRecognizer, panDimissAnimation: PanDimissAnimation?, dimissAnimation: DimissAnimation?) {
self.panGestureRecognizer = panGestureRecognizer
self.panDimissAnimation = panDimissAnimation
self.dimissAnimation = dimissAnimation
super.init()
self.completionSpeed = 0.2
panGestureRecognizer.addTarget(self, action: #selector(panAction(pan:)))
}
@objc private func panAction(pan: UIPanGestureRecognizer) {
guard let view = pan.view else {
return
}
let poin = pan.translation(in: view)
var progress: CGFloat = 0
var cancelProgress: CGFloat = 0.4
if let panDimissAnimation = self.panDimissAnimation {
let (tProgress, tCancelProgress) = panDimissAnimation(poin, pan.view!.frame)
progress = tProgress
cancelProgress = tCancelProgress
}
switch pan.state {
case .began:
pan.setTranslation(CGPoint(x: 0, y: 0), in: view)
case .changed:
update(progress)
case .cancelled:
cancel()
case .ended:
if progress > cancelProgress {
finish()
dimissAnimation?(false)
} else {
cancel()
dimissAnimation?(true)
}
default:
break
}
}
override func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) {
super.startInteractiveTransition(transitionContext)
}
deinit {
panGestureRecognizer.removeTarget(self, action: #selector(panAction(pan:)))
}
}
class SSAlertPresentAnimation: NSObject {
typealias AnimationCompletion = () -> Void
var duration: TimeInterval = 0.35
private var showAnimation: AnimationCompletion? = nil
private var hideAnimation: AnimationCompletion? = nil
private var endCompletion: AnimationCompletion? = nil
private var panDimissAnimation: PanDimissAnimation? = nil
private var dimissAnimation: DimissAnimation? = nil
private var transitionContext: UIViewControllerContextTransitioning?
private weak var nav: UINavigationController?
private weak var animationView: UIView?
private var panGestureRecognizer: UIPanGestureRecognizer?
init(navigationViewController: UINavigationController, animationView: UIView, canPanDimiss: Bool = true) {
self.nav = navigationViewController
self.animationView = animationView
super.init()
if canPanDimiss {
let pan = UIPanGestureRecognizer(target: self, action: #selector(panAction(pan:)))
pan.maximumNumberOfTouches = 1
animationView.addGestureRecognizer(pan)
}
}
@objc func panAction(pan: UIPanGestureRecognizer) {
if pan.state == .began {
panGestureRecognizer = pan
nav?.viewControllers[0].dismiss(animated: true, completion: {
[weak self] in guard let self = self else { return }
self.panGestureRecognizer = nil
});
}
}
func animationCompletion(showAnimation: AnimationCompletion? = nil,
hideAnimation: AnimationCompletion? = nil,
endCompletion: AnimationCompletion? = nil,
panDimissAnimation: PanDimissAnimation? = nil,
dimissAnimation: DimissAnimation? = nil) {
if showAnimation != nil {
self.showAnimation = showAnimation
}
if hideAnimation != nil {
self.hideAnimation = hideAnimation
}
if endCompletion != nil {
self.endCompletion = endCompletion
}
if panDimissAnimation != nil {
self.panDimissAnimation = panDimissAnimation
}
if panDimissAnimation != nil {
self.panDimissAnimation = panDimissAnimation
}
if dimissAnimation != nil {
self.dimissAnimation = dimissAnimation
}
}
@discardableResult
func setCompleteTransitionIsHide(isHide: Bool) -> Bool {
if let transitionContext = transitionContext {
let isCancelled = transitionContext.transitionWasCancelled
if !isCancelled {
if isHide {
transitionContext.containerView.subviews.forEach { subView in
subView.removeFromSuperview()
}
}
}
transitionContext.completeTransition(!isCancelled)
return isCancelled
}
return false
}
}
extension SSAlertPresentAnimation: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
if self.panGestureRecognizer != nil {
return SSCustomInteractiveAnimation(panGestureRecognizer: self.panGestureRecognizer!, panDimissAnimation: self.panDimissAnimation, dimissAnimation: dimissAnimation)
}
return nil
}
func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
if self.panGestureRecognizer != nil {
return SSCustomInteractiveAnimation(panGestureRecognizer: self.panGestureRecognizer!, panDimissAnimation: self.panDimissAnimation, dimissAnimation: dimissAnimation)
}
return nil
}
}
extension SSAlertPresentAnimation: UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return self.duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
self.transitionContext = transitionContext
let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)
let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
let containerView = transitionContext.containerView
if let fromVC = fromVC, let toVC = toVC {
var fromView = fromVC.view
var toView = toVC.view
if transitionContext.responds(to: #selector(UIViewControllerContextTransitioning.view(forKey:))) {
fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)
toView = transitionContext.view(forKey: UITransitionContextViewKey.to)
}
let isPresenting = toVC.presentingViewController == fromVC
if isPresenting {
if let toView = toView {
containerView.addSubview(toView)
}
if let showAnimation = self.showAnimation {
showAnimation()
}
} else {
if let toView = toView, let fromView = fromView {
containerView.insertSubview(toView, belowSubview: fromView)
}
if let hideAnimation = self.hideAnimation {
hideAnimation()
}
}
}
}
func animationEnded(_ transitionCompleted: Bool) {
if let endCompletion = self.endCompletion, transitionCompleted, let transitionContext = transitionContext, transitionContext.isInteractive {
endCompletion()
}
}
}
@@ -0,0 +1,312 @@
//
// SSAlertView.swift
// SSAlert
//
// Created by yangsq on 2021/8/13.
//
import UIKit
extension SSAlertView {
public enum BackgroundMaskType {
///
case none
///
case clear
///
case black
}
}
fileprivate extension SSAlertView {
typealias HideCompletion = () -> Void
class HideCompletionData: NSObject {
var completion: HideCompletion?
}
}
open class SSAlertView: UIView {
/// customView
public var customEdgeInsets: UIEdgeInsets = .zero {
didSet {
setNeedsDisplay()
layoutIfNeeded()
}
}
///
public var animation: SSAlertAnimation
///
public private(set) var backgroundMask: UIView?
///
public var canTouchMaskHide: Bool = true
/// nil,
public var isTouchMaskHideAnimated: Bool? = nil
///
public private(set) var navigationController: UINavigationController?
public var isHideStatusBar = false {
didSet {
self.toViewContrller?.isHideStatusBar = isHideStatusBar
}
}
public var isShowNavWhenViewWillDisappear = true {
didSet {
self.toViewContrller?.isShowNavWhenViewWillDisappear = isShowNavWhenViewWillDisappear
}
}
private var onView: UIView
public private(set) var maskType: BackgroundMaskType
public private(set) var customView: UIView
private weak var fromViewController: UIViewController?
private weak var toViewContrller: SSAlertAnimationController?
private var presentAnimation: SSAlertPresentAnimation?
private var canPanDimiss: Bool = false
private var hideCompletions = [HideCompletionData]()
///
public init(customView: UIView,
onView: UIView,
animation: SSAlertAnimation = SSAlertDefaultAnmation(state: .fromCenter),
maskType: BackgroundMaskType = .black) {
self.animation = animation
self.onView = onView
self.maskType = maskType
self.customView = customView
super.init(frame: .zero)
makeUI()
}
/// ),canPanDimiss:
public convenience init(customView: UIView,
fromViewController: UIViewController,
animation: SSAlertAnimation = SSAlertDefaultAnmation(state: .fromCenter),
navigationControllerClass: UINavigationController.Type = UINavigationController.self,
maskType: BackgroundMaskType = .black,
canPanDimiss: Bool = true) {
let animaionVC = SSAlertAnimationController()
self.init(customView: customView, onView: animaionVC.view, animation: animation, maskType: maskType)
animaionVC.isHideStatusBar = self.isHideStatusBar
if let superViewController = superViewController(view: customView), superViewController != animaionVC {
animaionVC.addChild(superViewController)
superViewController.didMove(toParent: animaionVC)
}
self.canPanDimiss = canPanDimiss
self.fromViewController = fromViewController
let nav = navigationControllerClass.init(rootViewController: animaionVC)
nav.modalPresentationStyle = .custom
self.navigationController = nav
self.canPanDimiss = canPanDimiss
let animation = SSAlertPresentAnimation(navigationViewController: nav, animationView: customView, canPanDimiss: canPanDimiss)
self.navigationController!.transitioningDelegate = animation
self.navigationController!.modalPresentationCapturesStatusBarAppearance = true
self.presentAnimation = animation
self.toViewContrller = animaionVC
}
open override func layoutSubviews() {
super.layoutSubviews()
backgroundMask?.frame = CGRect(x: 0, y: 0, width: onView.ss_w, height: onView.ss_h)
customView.ss_x = customEdgeInsets.left
customView.ss_y = customEdgeInsets.top
customView.ss_w = frame.width - customEdgeInsets.left - customEdgeInsets.right
customView.ss_h = frame.height - customEdgeInsets.top - customEdgeInsets.bottom
}
private func makeUI() {
if maskType != .none {
let backgroundMask = UIButton()
backgroundMask.alpha = 0
onView.addSubview(backgroundMask)
backgroundMask.addTarget(self, action: #selector(backgroundMaskAction), for: .touchUpInside)
self.backgroundMask = backgroundMask
if maskType == .black {
backgroundMask.backgroundColor = UIColor.black.withAlphaComponent(0.3)
}
}
addSubview(customView)
onView.addSubview(self)
}
///
public func show(animated: Bool = true, completion: (() -> Void)? = nil) {
if navigationController != nil {
presentView(animated: animated, completion: completion)
} else {
showView(animated: animated, completion: completion)
}
}
///
public func hide(animated: Bool = true, completion: (() -> Void)? = nil) {
if navigationController != nil {
dimissView(animated: animated, completion: completion)
} else {
hideView(animated: animated, completion: completion)
}
}
///
/// - Parameter animated:
private func showView(animated: Bool = true, completion: (() -> Void)? = nil) {
isTouchMaskHideAnimated = animated
var customSize = customView.systemLayoutSizeFitting(UIView.layoutFittingExpandedSize)
if customView.ss_size != .zero {
customSize = customView.ss_size
}
let size = CGSize(width: customSize.width + customEdgeInsets.left + customEdgeInsets.right, height: customSize.height + customEdgeInsets.top + customEdgeInsets.bottom)
animation.showAnimationOfAnimationView(animationView: self, viewSize: size, animated: animated, completion: { _ in completion?() })
}
///
/// - Parameter animated:
private func hideView(animated: Bool = true, completion: (() -> Void)? = nil) {
var customSize = customView.systemLayoutSizeFitting(UIView.layoutFittingExpandedSize)
if customView.ss_size != .zero {
customSize = customView.ss_size
}
let size = CGSize(width: customSize.width + customEdgeInsets.left + customEdgeInsets.right, height: customSize.height + customEdgeInsets.top + customEdgeInsets.bottom)
animation.hideAnimationOfAnimationView(animationView: self, viewSize: size, animated: animated) { [weak self] _ in guard let self = self else { return false }
completion?()
self.hideCompletions.forEach { hideC in
hideC.completion?()
hideC.completion = nil
self.hideCompletions.removeAll(where: {$0 == hideC})
}
return false
}
}
///
/// - Parameter animated:
private func presentView(animated: Bool = true, completion: (() -> Void)? = nil) {
isTouchMaskHideAnimated = animated
var customSize = customView.systemLayoutSizeFitting(UIView.layoutFittingExpandedSize)
if customView.ss_size != .zero {
customSize = customView.ss_size
}
let size = CGSize(width: customSize.width + customEdgeInsets.left + customEdgeInsets.right, height: customSize.height + customEdgeInsets.top + customEdgeInsets.bottom)
presentAnimation?.animationCompletion(showAnimation: {
[weak self] in guard let self = self else { return }
self.animation.showAnimationOfAnimationView(animationView: self, viewSize: size, animated: animated) { _ in
self.presentAnimation?.setCompleteTransitionIsHide(isHide: false)
completion?()
}
})
self.presentAnimation?.duration = animation.animationDuration()
/// apppresnentmodalPresentationStyleCurrentContextpresentingVCtabbarVCtabbarVCdimisspresentedVCdimissdimisspresentigngVCdimiss,
/// definesPresentationContext = true,modalPresentationStyle = UIModalPresentationOverCurrentContextpresentigngVC self.navigationController,
/// UIModalPresentationOverCurrentContext UIModalPresentationCurrentContextstact
self.navigationController?.definesPresentationContext = true
self.navigationController?.modalPresentationStyle = .overCurrentContext
self.fromViewController?.present(self.navigationController!, animated: true, completion: nil)
if canPanDimiss {
dimissViewDragToHideAnimation(animated: animated)
}
}
///
/// - Parameter animated:
private func dimissView(animated: Bool = true, completion: (() -> Void)? = nil) {
var customSize = customView.systemLayoutSizeFitting(UIView.layoutFittingExpandedSize)
if customView.ss_size != .zero {
customSize = customView.ss_size
}
let size = CGSize(width: customSize.width + customEdgeInsets.left + customEdgeInsets.right, height: customSize.height + customEdgeInsets.top + customEdgeInsets.bottom)
presentAnimation?.animationCompletion(hideAnimation: {
[weak self] in guard let self = self else { return }
self.animation.hideAnimationOfAnimationView(animationView: self, viewSize: size, animated: animated) { _ in
return self.presentAnimation!.setCompleteTransitionIsHide(isHide: true)
}
})
self.presentAnimation?.duration = animation.animationDuration()
self.toViewContrller?.dismiss(animated: animated, completion: {
[weak self] in guard let self = self else { return }
completion?()
self.hideCompletions.forEach { hideC in
hideC.completion?()
hideC.completion = nil
self.hideCompletions.removeAll(where: {$0 == hideC})
}
})
}
private func dimissViewDragToHideAnimation(animated: Bool) {
var customSize = customView.systemLayoutSizeFitting(UIView.layoutFittingExpandedSize)
if customView.ss_size != .zero {
customSize = customView.ss_size
}
let size = CGSize(width: customSize.width + customEdgeInsets.left + customEdgeInsets.right, height: customSize.height + customEdgeInsets.top + customEdgeInsets.bottom)
presentAnimation?.animationCompletion(hideAnimation: {
[weak self] in guard let self = self else { return }
self.animation.hideAnimationOfAnimationView(animationView: self, viewSize: size, animated: animated) { _ in
return self.presentAnimation!.setCompleteTransitionIsHide(isHide: true)
}
},endCompletion: {
[weak self] in guard let self = self else { return }
self.hideCompletions.forEach { hideC in
hideC.completion?()
hideC.completion = nil
self.hideCompletions.removeAll(where: {$0 == hideC})
}
}, panDimissAnimation: { [weak self] point, frame in
guard let self = self else { return (0, 0.4) }
return self.animation.panToDimissTransilatePoint(point: point, panViewFrame: frame)
}, dimissAnimation: { [weak self] isCancel in guard let self = self else { return }
self.toViewContrller?.isDimiss = !isCancel
})
self.presentAnimation?.duration = animation.animationDuration()
}
public func refreshFrame(animated: Bool = true) {
var customSize = customView.systemLayoutSizeFitting(UIView.layoutFittingExpandedSize)
if customView.ss_size != .zero {
customSize = customView.ss_size
}
let size = CGSize(width: customSize.width + customEdgeInsets.left + customEdgeInsets.right, height: customSize.height + customEdgeInsets.top + customEdgeInsets.bottom)
animation.refreshAnimationOfAnimationView(animationView: self, viewSize: size, animated: animated, completion: nil)
}
@objc private func backgroundMaskAction() {
if canTouchMaskHide {
if fromViewController != nil {
dimissView(animated: isTouchMaskHideAnimated ?? false)
} else {
hideView(animated: isTouchMaskHideAnimated ?? false)
}
}
}
private func superViewController(view: UIView) -> UIViewController? {
var next = view.next
while next != nil {
if next is UIViewController {
return next as? UIViewController
} else {
next = next!.next
}
}
return nil
}
public func observeHideCompletion(completion: (() -> Void)?) {
let completionData = HideCompletionData()
completionData.completion = completion
self.hideCompletions.append(completionData)
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
@@ -0,0 +1,127 @@
//
// SSAlertViewExtention.swift
// SSAlert
//
// Created by yangsq on 2021/8/13.
//
import Foundation
extension SSAlertView {
@discardableResult
public class func alertView(style: SSAlertAction.Style, title: String? = nil, message: String? = nil, cancelButton: String? = nil, otherButtons: [String] = [], onView: UIView, onTrigger:((Int) -> Void)? = nil) -> SSAlertView {
var actionArray = [SSAlertAction]()
var cancelAction: SSAlertAction? = nil
var alertView: SSAlertView?
if style == .alert {
if let cancelButton = cancelButton, !cancelButton.isEmpty {
cancelAction = SSAlertAction(style: style, title: cancelButton, addAction: {
if let onTrigger = onTrigger {
onTrigger(0)
}
alertView?.hide()
})
actionArray.append(cancelAction!)
}
otherButtons.forEach { title in
let action = SSAlertAction(style: style, title: title) {
if let onTrigger = onTrigger {
let index = otherButtons.firstIndex(of: title)
onTrigger(cancelAction != nil ? (index! + 1) : index!)
}
}
actionArray.append(action)
}
}
if style == .actionSheet {
otherButtons.forEach { title in
let action = SSAlertAction(style: style, title: title) {
if let onTrigger = onTrigger {
let index = otherButtons.firstIndex(of: title)
onTrigger(cancelAction != nil ? (index! + 1) : index!)
}
}
actionArray.append(action)
}
if let cancelButton = cancelButton, !cancelButton.isEmpty {
cancelAction = SSAlertAction(style: style, title: cancelButton, addAction: {
if let onTrigger = onTrigger {
onTrigger(0)
}
alertView?.hide()
})
actionArray.append(cancelAction!)
}
}
let customView = SSAlertCommonView(title: title, message: message, style: style, actions: actionArray)
customView.backgroundColor = .white
alertView = SSAlertView(customView: customView, onView: onView, animation: SSAlertDefaultAnmation(state: style == .actionSheet ? .fromBottom : .fromCenter), maskType: .black)
alertView!.canTouchMaskHide = false
if style == .alert {
alertView!.layer.cornerRadius = 10
alertView!.layer.masksToBounds = true
}
return alertView!
}
@discardableResult
public class func modalAlertView(style: SSAlertAction.Style, title: String? = nil, message: String? = nil, cancelButton: String? = nil, otherButtons: [String] = [], fromViewController: UIViewController, navigationViewControllerClass: UINavigationController.Type = UINavigationController.self, onTrigger:((Int) -> Void)? = nil) -> SSAlertView {
var actionArray = [SSAlertAction]()
var cancelAction: SSAlertAction? = nil
var alertView: SSAlertView?
if style == .alert {
if let cancelButton = cancelButton, !cancelButton.isEmpty {
cancelAction = SSAlertAction(style: style, title: cancelButton, addAction: {
if let onTrigger = onTrigger {
onTrigger(0)
}
alertView?.hide()
})
actionArray.append(cancelAction!)
}
otherButtons.forEach { title in
let action = SSAlertAction(style: style, title: title) {
if let onTrigger = onTrigger {
let index = otherButtons.firstIndex(of: title)
onTrigger(cancelAction != nil ? (index! + 1) : index!)
}
}
actionArray.append(action)
}
}
if style == .actionSheet {
otherButtons.forEach { title in
let action = SSAlertAction(style: style, title: title) {
if let onTrigger = onTrigger {
let index = otherButtons.firstIndex(of: title)
onTrigger(cancelAction != nil ? (index! + 1) : index!)
}
}
actionArray.append(action)
}
if let cancelButton = cancelButton, !cancelButton.isEmpty {
cancelAction = SSAlertAction(style: style, title: cancelButton, addAction: {
if let onTrigger = onTrigger {
onTrigger(0)
}
alertView?.hide()
})
actionArray.append(cancelAction!)
}
}
let customView = SSAlertCommonView(title: title, message: message, style: style, actions: actionArray)
customView.backgroundColor = .white
alertView = SSAlertView(customView: customView,fromViewController: fromViewController, animation: SSAlertDefaultAnmation(state: style == .actionSheet ? .fromBottom : .fromCenter), navigationControllerClass: navigationViewControllerClass, maskType: .black)
alertView!.canTouchMaskHide = false
if style == .alert {
alertView!.layer.cornerRadius = 10
alertView!.layer.masksToBounds = true
}
return alertView!
}
}
@@ -0,0 +1,132 @@
//
// UIViewFrameExtension.swift
// Pods-SSAlertSwiftDemo
//
// Created by yangsq on 2021/8/13.
//
import UIKit
public extension UIView {
var ss_size: CGSize {
get {
frame.size
}
set {
var frame = frame
frame.size = newValue
self.frame = frame
}
}
var ss_origin: CGPoint {
get {
frame.origin
}
set {
var frame = frame
frame.origin = newValue
self.frame = frame
}
}
var ss_center: CGPoint {
get {
center
}
set {
center = newValue
}
}
var ss_x: CGFloat {
get {
frame.origin.x
}
set {
var frame = frame
frame.origin.x = newValue
self.frame = frame
}
}
var ss_maxX: CGFloat {
get {
frame.maxX
}
}
var ss_y: CGFloat {
get {
frame.origin.y
}
set {
var frame = frame
frame.origin.y = newValue
self.frame = frame
}
}
var ss_maxY: CGFloat {
get {
frame.maxY
}
set {
var frame = frame
frame.origin.y = newValue
self.frame = frame
}
}
var ss_w: CGFloat {
get {
frame.width
}
set {
var frame = frame
frame.size.width = newValue
self.frame = frame
}
}
var ss_h: CGFloat {
get {
frame.height
}
set {
var frame = frame
frame.size.height = newValue
self.frame = frame
}
}
var ss_centerX: CGFloat {
get {
center.x
}
set {
var center = center
center.x = newValue
self.center = center
}
}
var ss_centerY: CGFloat {
get {
center.y
}
set {
var center = center
center.y = newValue
self.center = center
}
}
func ss_moveToCenter() {
if let superV = superview {
ss_center = CGPoint(x: superV.frame.width / 2, y: superV.frame.height / 2)
}
}
}