美文网首页Swift编程
如何设计一个优雅的弹框,Swift版

如何设计一个优雅的弹框,Swift版

作者: 何以消摇 | 来源:发表于2020-05-06 19:40 被阅读0次

    理念:爽到使用者就行了

    最爽的 调用弹框,那肯定是:Alert().show()
    什么,你要手动隐藏?那我再提供一个 dismiss()函数吧。
    什么,你还要改背景颜色,弹框动画的方向,还要能自定义……

    思路:通过协议的方式,提供默认实现

    行行行,都满足你。我提供一个协议给你,然后帮你实现默认的动画,剩下的你自己发挥想象力就好了。

    协议(AlertProtocol.swift):
    public enum AppearFrom {
        case top, bottom, left, right
    }
    
    // Protocol for showing and dissmising alert view
    public protocol AlertProtocol {
        func show(animated:Bool) -> Self
        func dismiss(animated:Bool) -> Self
        var backgroundView: UIView {get}
        var dialogView: UIView {get set}
        var appearFrom: AppearFrom {get}
        var clearBackground: Bool {get}
    }
    
    extension AlertProtocol {
        @discardableResult
        public func show() -> Self {
            return show(animated: true)
        }
        @discardableResult
        public func dismiss() -> Self {
            return dismiss(animated: true)
        }
    }
    
    默认实现 showdismiss
    
    extension AlertProtocol where Self: UIView{
        
        @discardableResult
        public func show(animated: Bool) -> Self {
            return show(animated: animated, superview: nil)
        }
        
        @discardableResult
        public func show(animated: Bool, superview: UIView?) -> Self {
            
            // Set origin before Animation
            if appearFrom == .top {
                self.dialogView.center = CGPoint(x:self.center.x, y:-self.frame.height+self.dialogView.frame.size.height/2)
            }else if appearFrom == .bottom {
                self.dialogView.center = CGPoint(x:self.center.x, y:self.frame.height+self.dialogView.frame.size.height/2)
            }else if appearFrom == .left {
                self.dialogView.center = CGPoint(x:-self.frame.size.width, y:self.frame.height/2)
            }else {
                self.dialogView.center = CGPoint(x:self.frame.size.width, y:self.frame.height/2)
            }
            
            self.backgroundView.alpha = 0
            self.backgroundView.isHidden = false
            if superview != nil {
                superview?.addSubview(self)
            }else {
                let cv = UIViewController.currentViewController()
                if let nav = cv?.navigationController {
                    nav.view.addSubview(self)
                }else {
                    cv?.view.addSubview(self)
                }
            }
            if animated {
                
                UIView.animate(withDuration: 0.33, animations: {
                    if self.clearBackground == true{
                        self.backgroundView.alpha = 0
                    }else{
                        self.backgroundView.alpha = 0.6
                    }
                })
                // Set origin during Animation
                UIView.animate(withDuration: 0.33, delay:0, usingSpringWithDamping:0.7, initialSpringVelocity:10, options:UIView.AnimationOptions(rawValue:0), animations: {
                    self.dialogView.center = self.center
                })
            }else{
                if self.clearBackground == true{
                    self.backgroundView.alpha = 0
                }else{
                    self.backgroundView.alpha = 0.6
                }
                self.dialogView.center = self.center
            }
            return self
        }
        
        @discardableResult
        public func dismiss() -> Self {
            return dismiss(animated: true)
        }
        
        @discardableResult
        public func dismiss(animated: Bool) -> Self {
            
            self.backgroundView.isHidden = true
            if animated {
                UIView.animate(withDuration: 0.33, animations: {
                    self.backgroundView.alpha = 0
                })
                
                UIView.animate(withDuration: 0.33, delay:0, usingSpringWithDamping: 1, initialSpringVelocity:10, options:UIView.AnimationOptions(rawValue:0), animations: {
                    
                    if self.appearFrom == .top {
                        self.dialogView.center = CGPoint(x:self.center.x, y:-self.frame.height+self.backgroundView.frame.size.height/2)
                    }else if self.appearFrom == .bottom {
                        self.dialogView.center = CGPoint(x:self.center.x, y:self.frame.height+self.dialogView.frame.size.height/2)
                    }else if self.appearFrom == .left {
                        self.dialogView.center = CGPoint(x:-self.frame.size.width, y:self.frame.height/2)
                    }else {
                        self.dialogView.center = CGPoint(x:self.frame.size.width+self.dialogView.frame.size.width, y:self.frame.height/2)
                    }
                    
                    
                }) { (completed) in
                    self.removeFromSuperview()
                }
                
            }else{
                self.removeFromSuperview()
            }
            return self
        }
    }
    
    extension UIViewController {
        /// 获取当前最顶层的vc
        internal class func currentViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
            if let nav = base as? UINavigationController {
                return currentViewController(base: nav.visibleViewController)
            }
            if let tab = base as? UITabBarController {
                return currentViewController(base: tab.selectedViewController)
            }
            if let presented = base?.presentedViewController {
                return currentViewController(base: presented)
            }
            return base
        }
        
    }
    

    进化:提供一个容器

    使用者吐槽,你在逗我吗?这玩意怎么用啊,你这也太敷衍了吧,我还要写一大堆代码,才能用。
    好吧,那我给你一个默认的容器吧,你只要告诉我中间显示啥就可以了。
    调用1:

    let base = OrderPayView.nibView().setTotalL("1000")
    let al = AlertCustomView<OrderPayView>(base)
        .show(animated: true)
    
    base.payBtn.rx.tap.bind { [unowned al] in
        al.dismiss()
        print("pay click")
    }.disposed(by: bag)
    

    调用2:

    let base = UILabel()
    base.text = "1000"
    AlertCustomView(base, false).show(animated: true)
    

    效果:


    效果1
    效果2

    容器代码(AlertCustomView.swift):

    /// 自定义弹框
    open class AlertCustomView<Base: UIView>: UIView, AlertProtocol {
         
        /// 自定义的组件
        open var base: Base {
            get { return customView }
            set { customView = newValue }
        }
        
        // MARK: - datas
        public var appearFrom: AppearFrom = .right
        public var clearBackground = Bool()
        
        public var closeBtnClick: (() -> Void)?
        
        // MARK: - views
        public var backgroundView = UIView()
        public var dialogView = UIView()
        // 自定义的View
        fileprivate var customView: Base = Base()
        /// 是否隐藏close
        private var isHiddenClose: Bool = true
        private var closeBtn: UIButton! // 关闭
        
        // MARK: - leftStyle
        deinit {
            print("alertType deinit")
        }
        
        fileprivate override init(frame: CGRect) {
            super.init(frame: frame)
        }
        
        public required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            initViews()
            latoutUI()
        }
        
        public convenience init(_ customView: Base, _ isHiddenClose: Bool = true) {
            
            self.init(frame: UIScreen.main.bounds)
            self.customView = customView
            self.isHiddenClose = isHiddenClose
            initViews()
            latoutUI()
        }
        
        // MARK: - events
        
        // dismiss the alert view
        @objc public func didcloseBtnTapped() {
            dismiss(animated: true)
            closeBtnClick?()
        }
        
        @objc func backgroundViewTap() {
            print("backgroundViewTap")
        }
        
        fileprivate func latoutUI() {
            let cf = base.frame
            
            backgroundView.frame = frame
            addSubview(backgroundView)
            
            addSubview(dialogView)
            dialogView.addSubview(customView)
            dialogView.addSubview(closeBtn)
            
            dialogView.snp.makeConstraints { (make) in
                make.center.equalToSuperview()
            }
            
            customView.snp.makeConstraints { (make) in
                make.size.equalTo(cf.size).priority(.low)
                
                let bottom: CGFloat = isHiddenClose ? 0 : 54.5
                make.edges.equalTo(UIEdgeInsets(top: cf.origin.y, left: cf.origin.x, bottom: bottom, right: 0 ))
            }
            
            closeBtn.snp.makeConstraints { (make) in
                make.bottom.centerX.equalToSuperview()
                make.width.height.equalTo(44)
            }
            
        }
        
        // MARK: - initViews
        public func initViews() {
            
            backgroundView = {
                let backgroundView = UIView()
                backgroundView.backgroundColor = UIColor.black
                return backgroundView
            }()
            let tap = UITapGestureRecognizer(target: self,
                                             action: #selector(backgroundViewTap))
    
            backgroundView.addGestureRecognizer(tap)
            
            closeBtn = {
                let closeBtn = UIButton()
                closeBtn.setImage(UIImage(named: "close"), for: .normal)
                return closeBtn
            }()
            
            closeBtn.isHidden = isHiddenClose
            closeBtn.addTarget(self, action: #selector(didcloseBtnTapped), for: UIControl.Event.touchUpInside)
            
        }
    }
    

    什么,你还是觉得丑,想要一个能改的默认模版?喂喂喂,过分了,兄弟,是你要求要自定义的。
    既然写到了这里,我就勉为骑男的再帮你写个默认实现吧。

    超进化:提供默认弹框

    事先说好,我用到了snpkit,你要是没有导入,那自己计算frame吧

    假装是代码
    这个类有点长,放最后面吧。
    

    这里要说的一点是提供了一个样式类,都有默认值,需要修改的,设置一下内容的style就好了。
    AlertViewStyle.swift

    public struct AlertViewStyle {
        public var backgroundViewAlpha: CGFloat = 0.6
        public var dialogViewCornerRadius: CGFloat = 5
        
        public var titleLFont: CGFloat = 18
        public var titleLColor = UIColor.hex("#333333")
        public var messageLFont: CGFloat = 15
        public var messageLColor = UIColor.hex("#333333")
        public var textViewFont: CGFloat = 15
        public var textViewBorderColor = UIColor.hex("#F07D8A")
        public var textViewTextCorlr = UIColor.hex("#333333")
        
        public var cancelBtnTextColor = UIColor.hex("#999999")
        public var cancelBtnTextFont: CGFloat = 16
        public var cancelBtnNormalBgColor = UIColor.hex("#EEEFF1")
    //    public var cancelBtnHighlightedBgColor = UIColor.c192451
        
        public var doneBtnTextColor = UIColor.white
        public var doneBtnTextFont: CGFloat = 16
        public var doneBtnNormalBgColor = UIColor.hex("#F07D8A")
        public var doneBtnHighlightedBgColor = UIColor.hex("#F07D8A", alpha: 0.8)
        
        public var cornerRadius: CGFloat = 3
        
        public init() {}
    }
    
    

    效果图:


    image.png
    image.png

    什么,你还要加入业务?!

    究极进化:整合业务

    调用

    let al = AlertTipBusinessView.init(.giveupPay).show()
    al.base.doneBtnClick = { [unowned al] in
        al.dismiss(animated: true)
    }
    

    详细代码(AlertTipBusinessView.swift):

    
    /// 业务类型
    enum TipBusinessType {
        case giveupPay
        case addAddress
        case deleteAddress
        case cancelOrder
        case remindDeliverGoods
        case secendRemind
        
        struct Data {
            var title = ""
            var subTitle = ""
            var certain = ""
            var other = ""
            init(subTitle: String, centain: String = "确认", title: String = "", other: String = "") {
                self.title = title
                self.subTitle = subTitle
                self.certain = centain
                self.other = other
            }
            
            init() {
                self.init(subTitle: "")
            }
        }
        var data: Data {
            var data = Data(subTitle: "")
            switch self {
            case .giveupPay:
                data.title = "确认要离开吗?"
                data.subTitle = "心动美物,看中了就赶紧下手哦!"
                data.certain = "继续购物"
                data.other = "残忍离开"
            case .addAddress:
                data.title = "添加地址"
                data.subTitle = "您尚未添加收件地址"
                data.certain = "添加"
                data.other = "稍后"
            case .deleteAddress:
                data.title = "删除地址"
                data.subTitle = "删除地址后不能恢复,请谨慎操作"
                data.certain = "返回"
                data.other = "删除"
            case .cancelOrder:
                data.title = "确认收货"
                data.subTitle = "请在收到货之后再确认收货"
                data.certain = "确认收货"
                data.other = "取消"
            case .remindDeliverGoods:
                data.title = "提醒发货"
                data.subTitle = "平台已经收到您的提醒,会尽快发货,请注意查收系统消息和短信"
                data.certain = "我知道了"
            case .secendRemind:
                data.subTitle = "平台已经收到您的提醒\n请勿频繁提交发货提醒"
                data.certain = "我知道了"
            }
            return data
        }
    }
    
    /// 提示业务逻辑
    class AlertTipBusinessView: AlertCustomView<AlertTipView> {
        
        /// 普通用法
        /// AlertTipBusinessView.init(.secendRemind).show()
        ///
        /// 自定义按钮事件
        /// let al = AlertTipBusinessView.init(.secendRemind).show()
        /// al.base.doneBtnClick = { [unowned al] in
        ///    al.dismiss(animated: true)
        /// }
        convenience init(_ businessType: TipBusinessType) {
            let data = businessType.data
            let tipV = AlertTipView(title: data.title, message: data.subTitle, doneBtnTitle: data.certain, cancelBtnTitle: data.other)
            self.init(tipV)
            
            if data.other.isEmpty {
                tipV.doneBtnClick = { [unowned self] in
                    self.dismiss()
                }
            }else {
                tipV.cancelBtnClick = { [unowned self] in
                    self.dismiss()
                }
            }
        }
    }
    

    合体:其他代码

    自定义UI代码(AlertTipView.swift):

    
    import Foundation
    import UIKit
    
    public class AlertTipView: UIView {
        
        // MARK: - datas
        public var style = AlertViewStyle() {
            didSet {
    
                titleL.font = UIFont.boldSystemFont(ofSize: style.titleLFont)
                titleL.textColor = style.titleLColor
                
                if titleL.text?.isEmpty ?? true {
                    messageL.font = UIFont.systemFont(ofSize: style.titleLFont)
                }else {
                    messageL.font = UIFont.systemFont(ofSize: style.messageLFont)
                }
                messageL.textColor = style.messageLColor
    
    
                doneBtn.setTitleColor(style.doneBtnTextColor, for: .normal)
                doneBtn.titleLabel?.font = UIFont.boldSystemFont(ofSize: style.doneBtnTextFont)
                doneBtn.setBackgroundImage(UIImage(color: style.doneBtnNormalBgColor),
                                           for: .normal)
                doneBtn.setBackgroundImage(UIImage(color: style.doneBtnHighlightedBgColor),
                                           for: .highlighted)
    
                cancelBtn.titleLabel?.font = UIFont.boldSystemFont(ofSize: style.cancelBtnTextFont)
    
                cancelBtn.setTitleColor(style.cancelBtnTextColor, for: .normal)
                cancelBtn.setBackgroundImage(UIImage(color: style.cancelBtnNormalBgColor),
                for: .normal)
            }
        }
        
        public var doneBtnClick: (() -> Void)?
        public var cancelBtnClick: (() -> Void)?
    
        // MARK: - views
        public var whiteBgV: UIView!
    
        private var titleL: UILabel!
        private var messageL: UILabel!
    
        public var cancelBtn: UIButton! // 取消
        public var doneBtn: UIButton!
    
        // MARK: - leftStyle
        deinit {
            print("alertType deinit")
        }
    
        public convenience init(title: String, message: String, doneBtnTitle: String, cancelBtnTitle: String = "取消") {
            self.init(frame: CGRect(x: 0, y: 0, width: 265, height: 0))
            self.initialise(title: title, message: message, doneBtnTitle: doneBtnTitle, cancelBtnTitle: cancelBtnTitle)
        }
    
        public override init(frame: CGRect) {
            style = AlertViewStyle()
            super.init(frame: frame)
            initViews()
        }
    
        public required init?(coder aDecoder: NSCoder) {
            style = AlertViewStyle()
            super.init(coder: aDecoder)
            initViews()
        }
    
        private func initialise(title: String, message: String, doneBtnTitle: String, cancelBtnTitle: String = "取消") {
    
            titleL.text = title
            doneBtn.setTitle(doneBtnTitle, for: UIControl.State.normal)
            cancelBtn.setTitle(cancelBtnTitle, for: UIControl.State.normal)
    
            messageL.text = message
            
            cancelBtn.addTarget(self, action: #selector(didCancelBtnTapped), for: UIControl.Event.touchUpInside)
            doneBtn.addTarget(self, action: #selector(didDoneBtnTappad), for: UIControl.Event.touchUpInside)
    
            let hasCancel = !cancelBtnTitle.isEmpty
            hasCancel ? setupTextAlert(!title.isEmpty) : setupNoCancelTextAlert(!title.isEmpty)
            
    
        }
    
        // MARK: - events
    
        @objc public func didDoneBtnTappad() {
            print("doneBtn isTappad,get btn use getdoneBtn()")
            doneBtnClick?()
        }
    
        // dismiss the alert view
        @objc public func didCancelBtnTapped() {
            cancelBtnClick?()
        }
    
        // MARK: - initViews
        public func initViews() {
            whiteBgV = {
                let view = UIView()
                view.backgroundColor = UIColor.white
                view.layer.cornerRadius = style.cornerRadius
                view.clipsToBounds = true
                return view
            }()
    
    
            titleL = {
                let titleL = UILabel()
                titleL.textAlignment = .center
                titleL.lineBreakMode = NSLineBreakMode.byWordWrapping
                titleL.numberOfLines = 0
                titleL.sizeToFit()
                return titleL
            }()
             messageL = {
                let messageL = UILabel()
                messageL.numberOfLines = 0
                messageL.lineBreakMode = NSLineBreakMode.byWordWrapping
                messageL.textAlignment = .center
                messageL.sizeToFit()
                return messageL
            }()
    
            cancelBtn = {
                let cancelBtn = UIButton()
                cancelBtn.layer.cornerRadius = style.cornerRadius
                cancelBtn.clipsToBounds = true
                return cancelBtn
            }()
    
            doneBtn = {
                let doneBtn = UIButton()
                //        doneBtn.backgroundColor =  UIColor.darkText
                doneBtn.layer.cornerRadius = style.cornerRadius
                doneBtn.clipsToBounds = true
                return doneBtn
            }()
        }
    
        public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            print("al touchesBegan")
        }
    
    }
    
    // MARK: - textAlert
    extension AlertTipView {
    
        fileprivate func setupTextAlert(_ hasTitle: Bool = true) {
    
            self.addSubview(whiteBgV)
            whiteBgV.addSubview(titleL)
            whiteBgV.addSubview(messageL)
            whiteBgV.addSubview(doneBtn)
            whiteBgV.addSubview(cancelBtn)
    
            whiteBgV.snp.makeConstraints { (make) in
                make.left.top.right.equalToSuperview()
                make.bottom.equalTo(0)
                make.width.equalTo(265)
            }
    
            titleL.snp.makeConstraints { (make) in
                make.top.equalTo(20)
                make.left.equalTo(15)
                make.right.equalTo(-15)
            }
    
            messageL.snp.makeConstraints { (make) in
                let top: CGFloat = hasTitle ? 60 : 40
                let bottom: CGFloat = hasTitle ? 70 : 84
                make.edges.equalTo(UIEdgeInsets(top: top, left: 20, bottom: bottom, right: 20))
            }
    
            doneBtn.snp.makeConstraints { (make) in
                make.height.equalTo(44)
                make.right.bottom.equalTo(0)
                make.width.equalToSuperview().multipliedBy(0.5)
            }
    
            cancelBtn.snp.makeConstraints { (make) in
                make.left.equalTo(0)
                make.bottom.width.height.equalTo(doneBtn)
            }
            doneBtn.clipsToBounds = false
            cancelBtn.clipsToBounds = false
        }
    
    
        fileprivate func setupNoCancelTextAlert(_ hasTitle: Bool = true) {
            
            self.addSubview(whiteBgV)
            whiteBgV.addSubview(titleL)
            whiteBgV.addSubview(messageL)
            whiteBgV.addSubview(doneBtn)
            whiteBgV.addSubview(cancelBtn)
    
            whiteBgV.snp.makeConstraints { (make) in
                make.left.top.right.equalToSuperview()
                make.bottom.equalTo(0)
                make.width.equalTo(265)
            }
    
            titleL.snp.makeConstraints { (make) in
                make.top.equalTo(20)
                make.left.equalTo(15)
                make.right.equalTo(-15)
            }
    
            messageL.snp.makeConstraints { (make) in
    
                let top: CGFloat = hasTitle ? 60 : 40
                let bottom: CGFloat = hasTitle ? 80 : 96
                make.edges.equalTo(UIEdgeInsets(top: top, left: 20, bottom: bottom, right: 20))
            }
    
            doneBtn.snp.makeConstraints { (make) in
    
                make.height.equalTo(36)
                make.bottom.equalTo(-20)
                make.left.equalTo(22.5)
                make.right.equalTo(-22.5)
            }
        }
    }
    

    项目中用到的小工具(AlertTools.swift)

    
    import Foundation
    
    //mark UIImage with downloadable content
    extension UIImage {
        
        //根据颜色创建图片
        public convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) {
            let rect = CGRect(origin: .zero, size: size)
            UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
            color.setFill()
            UIRectFill(rect)
            let image = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()
            
            guard let cgImage = image?.cgImage else { return nil }
            self.init(cgImage: cgImage)
        }
    }
    
    extension UIColor {
        
        public convenience init(hex: String) {
            self.init(hex: hex, alpha:1)
        }
        
        public convenience init(hex: String, alpha: CGFloat) {
            var hexWithoutSymbol = hex
            if hexWithoutSymbol.hasPrefix("#") {
                hexWithoutSymbol = hex.substring(from: 1)
            }
            
            let scanner = Scanner(string: hexWithoutSymbol)
            var hexInt:UInt32 = 0x0
            scanner.scanHexInt32(&hexInt)
            
            var r:UInt32!, g:UInt32!, b:UInt32!
            switch (hexWithoutSymbol.count) {
            case 3: // #RGB
                r = ((hexInt >> 4) & 0xf0 | (hexInt >> 8) & 0x0f)
                g = ((hexInt >> 0) & 0xf0 | (hexInt >> 4) & 0x0f)
                b = ((hexInt << 4) & 0xf0 | hexInt & 0x0f)
                break;
            case 6: // #RRGGBB
                r = (hexInt >> 16) & 0xff
                g = (hexInt >> 8) & 0xff
                b = hexInt & 0xff
                break;
            default:
                
                print("UIColor init error: hex == \(hex), alpha == \(alpha)")
                break;
            }
            
            self.init(
                red: (CGFloat(r)/255),
                green: (CGFloat(g)/255),
                blue: (CGFloat(b)/255),
                alpha:alpha)
        }
        
        public static func hex(_ hex: String) -> UIColor{
             return UIColor(hex: hex)
        }
        
        public static func hex(_ hex: String, alpha: CGFloat) -> UIColor{
            return UIColor(hex: hex, alpha: alpha)
        }
    
    }
    
    // MARK: - substring
    extension String {
        /// 返回Index类型
        ///
        public func index(from: Int) -> Index {
            return self.index(startIndex, offsetBy: from)
        }
        /// 裁剪字符串from
        ///
        /// - Parameter from: 从哪里开始
        public func substring(from: Int) -> String {
            let fromIndex = index(from: from)
    
            return String(suffix(from: fromIndex))
        }
    
        /// 裁剪字符串to
        ///
        /// - Parameter to: 到哪里结束
        public func substring(to: Int) -> String {
            let toIndex = index(from: to)
            return String(prefix(upTo: toIndex))
        }
    }
    

    相关文章

      网友评论

        本文标题:如何设计一个优雅的弹框,Swift版

        本文链接:https://www.haomeiwen.com/subject/mgfgghtx.html