jsdw_ios/QuickLocation/Component/ImagePicker/PopupViewController.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
}
}