595 lines
21 KiB
Swift
595 lines
21 KiB
Swift
//
|
|
// PopupViewController.swift
|
|
// JiuLaiBao
|
|
//
|
|
// Created by Dan Jiang on 2018/11/13.
|
|
// Copyright © 2018 GuoXiaoMei. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
import SnapKit
|
|
|
|
public struct PopupViewConfig {
|
|
static public var width: CGFloat = 280
|
|
static public var vMargin: CGFloat = 34
|
|
static public var hMargin: CGFloat = 20
|
|
static public var vSpace: CGFloat = 16
|
|
static public var buttonHeight: CGFloat = 50
|
|
static public var buttonBorderColor = UIColor.gray
|
|
static public var closeButtonVMargin: CGFloat = 46
|
|
static public var closeButtonInset: UIEdgeInsets = .init(top: 12, left: 12, bottom: 12, right: 12)
|
|
static public var headlineColor = UIColor.black
|
|
static public var headlineFont = UIFont.boldSystemFont(ofSize: 17)
|
|
static public var messageColor = UIColor.black
|
|
static public var messageFont = UIFont.systemFont(ofSize: 17)
|
|
static public var confirmColor = UIColor.black
|
|
static public var confirmFont = UIFont.boldSystemFont(ofSize: 16)
|
|
static public var cancelColor = UIColor.gray
|
|
static public var cancelFont = UIFont.boldSystemFont(ofSize: 16)
|
|
static public var textFieldHeight: CGFloat = 40
|
|
static public var textFieldBorderColor = UIColor.gray
|
|
static public var textFieldTextColor = UIColor.black
|
|
static public var textFieldTextFont = UIFont.systemFont(ofSize: 14)
|
|
static public var textFieldPlaceholderColor = UIColor.gray
|
|
static public var textFieldPlaceholderFont = UIFont.systemFont(ofSize: 14)
|
|
static public var textFieldTipsColor = UIColor.red
|
|
static public var textFieldTipsFont = UIFont.systemFont(ofSize: 11)
|
|
static public var textFieldTipsHMargin: CGFloat = 30
|
|
static public var textFieldTipsVMargin: CGFloat = 6
|
|
}
|
|
|
|
public class PopupAction {
|
|
|
|
public let image: UIImage?
|
|
public let attributedTitle: NSAttributedString?
|
|
public let handler: ((PopupAction) -> Void)?
|
|
public let autoDismiss: Bool
|
|
public let position: Position
|
|
|
|
public enum Style {
|
|
case confirm
|
|
case cancel
|
|
}
|
|
|
|
public enum Position {
|
|
case bottom
|
|
case topRight
|
|
}
|
|
|
|
convenience public init(title: String, style: Style, autoDismiss: Bool = true, handler: ((PopupAction) -> Void)? = nil) {
|
|
let attributedTitle: NSAttributedString?
|
|
switch style {
|
|
case .confirm:
|
|
attributedTitle = NSAttributedString(string: title,
|
|
attributes: [.font: PopupViewConfig.confirmFont,
|
|
.foregroundColor: PopupViewConfig.confirmColor])
|
|
case .cancel:
|
|
attributedTitle = NSAttributedString(string: title,
|
|
attributes: [.font: PopupViewConfig.cancelFont,
|
|
.foregroundColor: PopupViewConfig.cancelColor])
|
|
}
|
|
self.init(attributedTitle: attributedTitle, autoDismiss: autoDismiss, handler: handler)
|
|
}
|
|
|
|
convenience public init(image: UIImage?, autoDismiss: Bool = true, handler: ((PopupAction) -> Void)? = nil) {
|
|
self.init(attributedTitle: nil, image: image, position: .topRight, autoDismiss: autoDismiss, handler: handler)
|
|
}
|
|
|
|
public init(attributedTitle: NSAttributedString?, image: UIImage? = nil, position: Position = .bottom,
|
|
autoDismiss: Bool = true, handler: ((PopupAction) -> Void)? = nil) {
|
|
self.attributedTitle = attributedTitle
|
|
self.image = image
|
|
self.position = position
|
|
self.autoDismiss = autoDismiss
|
|
self.handler = handler
|
|
}
|
|
|
|
}
|
|
|
|
open class PopupViewController: UIViewController {
|
|
|
|
public enum Style {
|
|
case alert
|
|
case sheet
|
|
}
|
|
|
|
public enum OverlayStyle {
|
|
case transparent
|
|
case dark
|
|
case extraLightBlur
|
|
case lightBlur
|
|
case darkBlur
|
|
}
|
|
|
|
public var style: Style {
|
|
return inStyle
|
|
}
|
|
public var overlayStyle: OverlayStyle {
|
|
return inOverlayStyle
|
|
}
|
|
public var headline: String? {
|
|
get {
|
|
return headlineLabel?.text
|
|
}
|
|
set {
|
|
headlineLabel?.text = newValue
|
|
}
|
|
}
|
|
public var attributedHeadline: NSAttributedString? {
|
|
get {
|
|
return headlineLabel?.attributedText
|
|
}
|
|
set {
|
|
headlineLabel?.attributedText = newValue
|
|
}
|
|
}
|
|
public var message: String? {
|
|
get {
|
|
return messageLabel?.text
|
|
}
|
|
set {
|
|
messageLabel?.text = newValue
|
|
}
|
|
}
|
|
public var attributedMessage: NSAttributedString? {
|
|
get {
|
|
return messageLabel?.attributedText
|
|
}
|
|
set {
|
|
messageLabel?.attributedText = newValue
|
|
}
|
|
}
|
|
public var textField: UITextField? {
|
|
return inTextField
|
|
}
|
|
public var customView: UIView? {
|
|
return inCustomView
|
|
}
|
|
public var actions: [PopupAction] {
|
|
return inActions
|
|
}
|
|
public var presentAnimator: UIViewControllerAnimatedTransitioning?
|
|
public var dimsissAnimator: UIViewControllerAnimatedTransitioning?
|
|
public var completionCallback: (() -> Void)?
|
|
public var canDismissOnOverlay: Bool?
|
|
|
|
let inStyle: Style
|
|
let inOverlayStyle: OverlayStyle
|
|
var inCanDismissOnOverlay = true
|
|
var overlayView: UIView!
|
|
var contentView: UIView!
|
|
var headlineLabel: UILabel?
|
|
var messageLabel: UILabel?
|
|
var tipsLabel: UILabel?
|
|
var inTextField: UITextField?
|
|
var inCustomView: UIView?
|
|
var actionButtons: [UIButton] = []
|
|
var inActions: [PopupAction] = []
|
|
|
|
required public init?(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
public init(style: Style, overlayStyle: OverlayStyle = .dark) {
|
|
self.inStyle = style
|
|
self.inOverlayStyle = overlayStyle
|
|
super.init(nibName: nil, bundle: nil)
|
|
modalPresentationStyle = .overFullScreen
|
|
transitioningDelegate = self
|
|
}
|
|
|
|
deinit {
|
|
print("\(self) deinit")
|
|
}
|
|
|
|
public func addHeadline(_ headline: String) {
|
|
let attributedHeadline = NSAttributedString(string: headline,
|
|
attributes: [.font: PopupViewConfig.headlineFont,
|
|
.foregroundColor: PopupViewConfig.headlineColor])
|
|
addAttributedHeadline(attributedHeadline)
|
|
}
|
|
|
|
public func addAttributedHeadline(_ headline: NSAttributedString?) {
|
|
if headlineLabel == nil {
|
|
let label = UILabel()
|
|
label.attributedText = headline
|
|
headlineLabel = label
|
|
}
|
|
}
|
|
|
|
public func addMessage(_ message: String, alignment: NSTextAlignment = .left) {
|
|
let paragraphStyle = NSMutableParagraphStyle()
|
|
paragraphStyle.lineSpacing = 6
|
|
paragraphStyle.alignment = alignment
|
|
let attributedMessage = NSAttributedString(string: message,
|
|
attributes: [.font: PopupViewConfig.messageFont,
|
|
.foregroundColor: PopupViewConfig.messageColor,
|
|
.paragraphStyle: paragraphStyle])
|
|
addAttributedMessage(attributedMessage)
|
|
}
|
|
|
|
public func addAttributedMessage(_ message: NSAttributedString?) {
|
|
if messageLabel == nil {
|
|
let label = UILabel()
|
|
label.attributedText = message
|
|
label.numberOfLines = 0
|
|
label.lineBreakMode = .byCharWrapping
|
|
messageLabel = label
|
|
}
|
|
}
|
|
|
|
public func addTextField(configurationHandler: ((UITextField) -> Void)? = nil) {
|
|
if tipsLabel == nil {
|
|
tipsLabel = UILabel()
|
|
tipsLabel?.textAlignment = .center
|
|
}
|
|
if inTextField == nil {
|
|
let textField = UITextField()
|
|
textField.layer.borderColor = PopupViewConfig.textFieldBorderColor.cgColor
|
|
textField.layer.borderWidth = 0.5
|
|
textField.textColor = PopupViewConfig.textFieldTextColor
|
|
textField.font = PopupViewConfig.textFieldTextFont
|
|
let leftView = UIView(frame: .init(x: 0, y: 0, width: 10,
|
|
height: PopupViewConfig.textFieldHeight))
|
|
textField.leftView = leftView
|
|
textField.leftViewMode = .always
|
|
|
|
inTextField = textField
|
|
|
|
configurationHandler?(textField)
|
|
}
|
|
}
|
|
|
|
public func setTextFieldPlaceholder(_ placehoder: String?) {
|
|
let attributedPlaceholder = NSAttributedString(string: placehoder ?? "",
|
|
font: PopupViewConfig.textFieldPlaceholderFont,
|
|
color: PopupViewConfig.textFieldPlaceholderColor)
|
|
setTextFieldAttributedPlaceholder(attributedPlaceholder)
|
|
}
|
|
|
|
public func setTextFieldAttributedPlaceholder(_ placehoder: NSAttributedString?) {
|
|
inTextField?.attributedPlaceholder = placehoder
|
|
}
|
|
|
|
public func setTextFieldTips(_ tips: String?) {
|
|
let attributedTips = NSAttributedString(string: tips ?? "",
|
|
font: PopupViewConfig.textFieldTipsFont,
|
|
color: PopupViewConfig.textFieldTipsColor)
|
|
setTextFieldAttributedTips(attributedTips)
|
|
}
|
|
|
|
public func setTextFieldAttributedTips(_ tips: NSAttributedString?) {
|
|
tipsLabel?.attributedText = tips
|
|
}
|
|
|
|
public func addCustomView(configurationHandler: ((UIView) -> Void)) {
|
|
if inCustomView == nil {
|
|
let customView = UIView()
|
|
inCustomView = customView
|
|
|
|
configurationHandler(customView)
|
|
}
|
|
}
|
|
|
|
public func addAction(_ action: PopupAction) {
|
|
inActions.append(action)
|
|
}
|
|
|
|
public func hide(callback: (() -> Void)? = nil) {
|
|
if let callback = callback {
|
|
presentingViewController?.dismiss(animated: true, completion: {
|
|
callback()
|
|
if let completion = self.completionCallback {
|
|
completion()
|
|
}
|
|
})
|
|
} else {
|
|
presentingViewController?.dismiss(animated: true, completion: completionCallback)
|
|
}
|
|
}
|
|
|
|
open override func loadView() {
|
|
super.loadView()
|
|
}
|
|
|
|
open override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
view.backgroundColor = .clear
|
|
|
|
if let canDismissOnOverlay = canDismissOnOverlay {
|
|
inCanDismissOnOverlay = canDismissOnOverlay
|
|
} else {
|
|
switch style {
|
|
case .alert:
|
|
inCanDismissOnOverlay = false
|
|
case .sheet:
|
|
inCanDismissOnOverlay = true
|
|
}
|
|
}
|
|
|
|
layoutOverlayView()
|
|
layoutContentView()
|
|
}
|
|
|
|
func layoutOverlayView() {
|
|
switch overlayStyle {
|
|
case .transparent:
|
|
overlayView = UIView()
|
|
overlayView.backgroundColor = UIColor(white: 0, alpha: 0)
|
|
case .dark:
|
|
overlayView = UIView()
|
|
overlayView.backgroundColor = UIColor(white: 0, alpha: 0.65)
|
|
case .extraLightBlur:
|
|
let blurEffect = UIBlurEffect(style: .extraLight)
|
|
overlayView = UIVisualEffectView(effect: blurEffect)
|
|
case .lightBlur:
|
|
let blurEffect = UIBlurEffect(style: .light)
|
|
overlayView = UIVisualEffectView(effect: blurEffect)
|
|
case .darkBlur:
|
|
let blurEffect = UIBlurEffect(style: .dark)
|
|
overlayView = UIVisualEffectView(effect: blurEffect)
|
|
}
|
|
|
|
if inCanDismissOnOverlay {
|
|
overlayView.isUserInteractionEnabled = true
|
|
overlayView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(onDismiss)))
|
|
}
|
|
|
|
view.addSubview(overlayView)
|
|
|
|
overlayView.snp.makeConstraints { make in
|
|
make.edges.equalToSuperview()
|
|
}
|
|
}
|
|
|
|
func layoutContentView() {
|
|
contentView = UIView()
|
|
view.addSubview(contentView)
|
|
|
|
switch style {
|
|
case .alert:
|
|
contentView.backgroundColor = .white
|
|
contentView.layer.cornerRadius = 2
|
|
contentView.snp.makeConstraints { make in
|
|
make.center.equalToSuperview()
|
|
make.width.equalTo(PopupViewConfig.width)
|
|
}
|
|
layoutAlertStyle()
|
|
case .sheet:
|
|
contentView.snp.makeConstraints { make in
|
|
make.left.right.bottom.equalToSuperview()
|
|
}
|
|
layoutSheetStyle()
|
|
}
|
|
}
|
|
|
|
func layoutAlertStyle() {
|
|
var top = PopupViewConfig.vMargin
|
|
var actionOnlyInTopRight = true
|
|
if actions.contains(where: { $0.position == .topRight }) {
|
|
top = PopupViewConfig.closeButtonVMargin
|
|
}
|
|
if actions.contains(where: { $0.position == .bottom }) {
|
|
actionOnlyInTopRight = false
|
|
}
|
|
|
|
if let headlineLabel = headlineLabel {
|
|
contentView.addSubview(headlineLabel)
|
|
|
|
headlineLabel.snp.makeConstraints { make in
|
|
make.top.equalToSuperview().offset(top)
|
|
make.left.equalToSuperview().offset(PopupViewConfig.hMargin)
|
|
}
|
|
}
|
|
|
|
let hLine = UIView()
|
|
if !actionOnlyInTopRight {
|
|
hLine.backgroundColor = PopupViewConfig.buttonBorderColor
|
|
contentView.addSubview(hLine)
|
|
hLine.snp.makeConstraints { make in
|
|
make.height.equalTo(0.5)
|
|
make.right.left.equalToSuperview()
|
|
}
|
|
}
|
|
|
|
if let messageLabel = messageLabel {
|
|
contentView.addSubview(messageLabel)
|
|
|
|
messageLabel.snp.makeConstraints { make in
|
|
if let headlineLabel = headlineLabel {
|
|
make.top.equalTo(headlineLabel.snp.bottom).offset(PopupViewConfig.vSpace)
|
|
} else {
|
|
make.top.equalToSuperview().offset(top)
|
|
}
|
|
make.left.equalToSuperview().offset(PopupViewConfig.hMargin)
|
|
make.right.equalToSuperview().offset(-PopupViewConfig.hMargin)
|
|
if !actionOnlyInTopRight {
|
|
make.bottom.equalTo(hLine.snp.top).offset(-PopupViewConfig.vMargin)
|
|
} else {
|
|
make.bottom.equalToSuperview().offset(-PopupViewConfig.vSpace)
|
|
}
|
|
}
|
|
}
|
|
|
|
if let textField = inTextField {
|
|
contentView.addSubview(textField)
|
|
|
|
textField.snp.makeConstraints { make in
|
|
if let headlineLabel = headlineLabel {
|
|
make.top.equalTo(headlineLabel.snp.bottom).offset(PopupViewConfig.vSpace)
|
|
} else {
|
|
make.top.equalToSuperview().offset(PopupViewConfig.vMargin)
|
|
}
|
|
make.left.equalToSuperview().offset(PopupViewConfig.hMargin)
|
|
make.right.equalToSuperview().offset(-PopupViewConfig.hMargin)
|
|
make.height.equalTo(PopupViewConfig.textFieldHeight)
|
|
var tipsHeight: CGFloat = 0
|
|
if tipsLabel != nil {
|
|
tipsHeight = 15.0
|
|
}
|
|
if !actionOnlyInTopRight {
|
|
var bottomOffset = PopupViewConfig.vMargin + tipsHeight
|
|
make.bottom.equalTo(hLine.snp.top).offset(-bottomOffset)
|
|
} else {
|
|
var bottomOffset = PopupViewConfig.vSpace + tipsHeight
|
|
make.bottom.equalToSuperview().offset(-bottomOffset)
|
|
}
|
|
}
|
|
|
|
if let tipsLabel = tipsLabel {
|
|
contentView.addSubview(tipsLabel)
|
|
|
|
tipsLabel.snp.makeConstraints { make in
|
|
make.top.equalTo(textField.snp.bottom).offset(PopupViewConfig.textFieldTipsVMargin)
|
|
make.left.equalToSuperview().offset(PopupViewConfig.textFieldTipsHMargin)
|
|
make.right.equalToSuperview().offset(-PopupViewConfig.textFieldTipsHMargin)
|
|
}
|
|
}
|
|
}
|
|
|
|
if let customView = inCustomView {
|
|
contentView.addSubview(customView)
|
|
|
|
customView.snp.makeConstraints { make in
|
|
make.top.left.right.equalToSuperview()
|
|
if !actionOnlyInTopRight {
|
|
make.bottom.equalTo(hLine.snp.top)
|
|
} else {
|
|
make.bottom.equalToSuperview()
|
|
}
|
|
}
|
|
}
|
|
|
|
var actionsButtonInBottom: [UIButton] = []
|
|
for action in actions {
|
|
let button = UIButton()
|
|
button.addTarget(self, action: #selector(onTap), for: .touchUpInside)
|
|
contentView.addSubview(button)
|
|
actionButtons.append(button)
|
|
if action.position == .topRight {
|
|
button.setImage(action.image, for: .normal)
|
|
button.contentEdgeInsets = PopupViewConfig.closeButtonInset
|
|
button.snp.makeConstraints { make in
|
|
make.top.equalToSuperview()
|
|
make.right.equalToSuperview()
|
|
}
|
|
} else {
|
|
button.setAttributedTitle(action.attributedTitle, for: .normal)
|
|
actionsButtonInBottom.append(button)
|
|
}
|
|
}
|
|
|
|
var prevButton: UIButton?
|
|
for (index, button) in actionsButtonInBottom.enumerated() {
|
|
if let prevButton = prevButton {
|
|
let vLine = UIView()
|
|
vLine.backgroundColor = PopupViewConfig.buttonBorderColor
|
|
contentView.addSubview(vLine)
|
|
vLine.snp.makeConstraints { make in
|
|
make.width.equalTo(0.5)
|
|
make.top.equalTo(prevButton)
|
|
make.left.equalTo(prevButton.snp.right)
|
|
make.bottom.equalToSuperview()
|
|
}
|
|
button.snp.makeConstraints { make in
|
|
make.height.equalTo(PopupViewConfig.buttonHeight)
|
|
make.top.equalTo(hLine.snp.bottom)
|
|
make.left.equalTo(vLine.snp.right)
|
|
make.bottom.equalToSuperview()
|
|
make.width.equalTo(prevButton)
|
|
if index == actionsButtonInBottom.count - 1 {
|
|
make.right.equalToSuperview()
|
|
}
|
|
}
|
|
} else {
|
|
button.snp.makeConstraints { make in
|
|
make.height.equalTo(PopupViewConfig.buttonHeight)
|
|
make.top.equalTo(hLine.snp.bottom)
|
|
make.left.equalToSuperview()
|
|
make.bottom.equalToSuperview()
|
|
if index == actionsButtonInBottom.count - 1 {
|
|
make.right.equalToSuperview()
|
|
}
|
|
}
|
|
}
|
|
prevButton = button
|
|
}
|
|
|
|
}
|
|
|
|
func layoutSheetStyle() {
|
|
if let customView = inCustomView {
|
|
contentView.addSubview(customView)
|
|
|
|
customView.snp.makeConstraints { make in
|
|
make.edges.equalToSuperview()
|
|
}
|
|
}
|
|
}
|
|
|
|
@objc func onDismiss() {
|
|
hide()
|
|
}
|
|
|
|
@objc func onTap(button: UIButton) {
|
|
if let index = actionButtons.firstIndex(where: { $0 == button }) {
|
|
let action = actions[index]
|
|
if action.autoDismiss {
|
|
hide {
|
|
if let handler = action.handler {
|
|
handler(action)
|
|
}
|
|
}
|
|
} else {
|
|
if let handler = action.handler {
|
|
handler(action)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
extension PopupViewController: UIViewControllerTransitioningDelegate {
|
|
|
|
public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
|
if let presentAnimator = presentAnimator {
|
|
return presentAnimator
|
|
} else if style == .alert {
|
|
return PopupAlertAnimator()
|
|
} else if style == .sheet {
|
|
return PopupSheetAnimator()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
|
if let dimsissAnimator = dimsissAnimator {
|
|
return dimsissAnimator
|
|
} else if style == .alert {
|
|
let animator = PopupAlertAnimator()
|
|
animator.isPresented = false
|
|
return animator
|
|
} else if style == .sheet {
|
|
let animator = PopupSheetAnimator()
|
|
animator.isPresented = false
|
|
return animator
|
|
}
|
|
return nil
|
|
}
|
|
|
|
}
|
|
|
|
extension PopupViewController: PopupAnimatable {
|
|
|
|
public var animateContentView: UIView {
|
|
return contentView
|
|
}
|
|
|
|
public var animateOverlayView: UIView {
|
|
return overlayView
|
|
}
|
|
|
|
}
|