Swift-消息提示框

App中经常会看到各种各样的消息提示框,如下图消息提示一定遇到过:


FlyElephant.png

消息提示框经常需要设置的,文本框宽度,最大高度,箭头指示的高度和宽度,背景颜色,文字颜色,动画,动画时间,消失时间:

public struct FEPreferences {
    
    public struct Drawing {
        public var cornerRadius        = CGFloat(5)
        public var arrowHeight         = CGFloat(8)
        public var arrowWidth          = CGFloat(10)
        
        public var textInset           = CGFloat(8)
        public var maxTextWidth        = CGFloat(180)
        public var maxHeight           = CGFloat(160)
        public var minHeight           = CGFloat(40)
 
        public var backgroundColor     = UIColor.orange

        public var textAlignment       = NSTextAlignment.center
        public var textColor           = UIColor.white
        public var textBackGroundColor = UIColor.clear
        public var font                = UIFont.systemFont(ofSize: 14)
        public var message             = ""
        
        public var borderWidth         = CGFloat(1)
        public var borderColor         = UIColor.clear
        public var hasBorder           = false
    }
    
    public struct Positioning {
        public var targetPoint         = CGPoint.zero
        public var arrowPosition       = UIPopoverArrowDirection.up
    }
    
    public struct Animating {
        public var showDuration         = 0.25
        public var delayDuration        = 2.0
        public var dismissDuration      = 0.25
        public var shouldDismiss        = true
    }
    
    public var drawing      = Drawing()
    public var positioning  = Positioning()
    public var animating    = Animating()
    public var hasBorder : Bool {
        return drawing.borderWidth > 0 && drawing.borderColor != UIColor.clear
    }
    
    public init() {}
}

箭头上下左右的方向事件:

   @IBAction func tipAction(_ sender: UIButton) {
        var preference:FEPreferences = FEPreferences()
        preference.positioning.targetPoint = CGPoint(x: sender.center.x, y: sender.frame.maxY)
        preference.drawing.message = "《道德经》是春秋时期老子(李耳)的哲学作品,又称《道德真经》、《老子》、《五千言》、《老子五千文》-FlyElephant"
        preference.animating.shouldDismiss = false
        
        let tipView:FETipView = FETipView(preferences: preference)
        tipView.show()
    }
    
    @IBAction func downAction(_ sender: UIButton) {
        var preference:FEPreferences = FEPreferences()
        preference.positioning.targetPoint = CGPoint(x: sender.center.x, y: sender.frame.minY)
        preference.positioning.arrowPosition = UIPopoverArrowDirection.down
        preference.animating.shouldDismiss = false

        preference.drawing.message = "《颜氏家训》是汉民族历史上第一部内容丰富,体系宏大的家训,也是一部学术著作。作者颜之推,是南北朝时期著名的文学家、教育家。-FlyElephant"
        
        let tipView:FETipView = FETipView(preferences: preference)
        tipView.show()
    }

    @IBAction func leftAction(_ sender: UIButton) {
        var preference:FEPreferences = FEPreferences()
        preference.positioning.targetPoint = CGPoint(x: sender.frame.maxX, y: sender.center.y)
        preference.positioning.arrowPosition = UIPopoverArrowDirection.left
        preference.animating.shouldDismiss = false

        preference.drawing.message = "理想国-FlyElephant"
        
        let tipView:FETipView = FETipView(preferences: preference)
        tipView.show()
    }
    
    @IBAction func rightAction(_ sender: UIButton) {
        var preference:FEPreferences = FEPreferences()
        preference.positioning.targetPoint = CGPoint(x: sender.frame.minX, y: sender.center.y)
        preference.positioning.arrowPosition = UIPopoverArrowDirection.right
        preference.animating.shouldDismiss = false
        
        preference.drawing.message = "《解忧杂货店》是日本作家东野圭吾写作的奇幻温情小说。2011年于《小说野性时代》连载,于2012年3月由角川书店发行单行本。该书讲述了在僻静街道旁的一家杂货店,只要写下烦恼投进店前门卷帘门的投信口,第二天就会在店后的牛奶箱里得到回答:因男友身患绝症,年轻女孩月兔在爱情与梦想间徘徊;松冈克郎为了音乐梦想离家漂泊,却在现实中寸步难行;少年浩介面临家庭巨变,挣扎在亲情与未来的迷茫中……他们将困惑写成信投进杂货店,奇妙的事情随即不断发生"
        
        let tipView:FETipView = FETipView(preferences: preference)
        tipView.show()
    }

完整实现代码:

class FETipView:UIView {
    
    private let screenWidth:CGFloat = UIScreen.main.bounds.width
    private let screenHeight:CGFloat = UIScreen.main.bounds.height
    
    private var label:UILabel!
    private var message:String = ""
    private var preference:FEPreferences!
    
    private var arrowHeight:CGFloat = 0
    private var arrowWidth:CGFloat = 0
    private var width:CGFloat = 0
    private var point:CGPoint = .zero
    
    private var textSize:CGSize = .zero
    private var contentSize:CGSize = .zero
    
    private lazy var contenLabel:UILabel = {
        var   label:UILabel = UILabel.init()
        label.lineBreakMode = NSLineBreakMode.byTruncatingTail
        label.numberOfLines = 0
        return label
    }()
    
    convenience init(preferences:FEPreferences) {
        self.init()
        self.preference = preferences
        
        point = preference.positioning.targetPoint
        arrowHeight = preference.drawing.arrowHeight
        arrowWidth = preference.drawing.arrowWidth
        
        switch preference.positioning.arrowPosition {
        case UIPopoverArrowDirection.up,UIPopoverArrowDirection.down:
              width = preference.drawing.maxTextWidth + preference.drawing.textInset * 2
        case UIPopoverArrowDirection.left,UIPopoverArrowDirection.right:
            width = preference.drawing.maxTextWidth + preference.drawing.textInset * 2 + arrowWidth
        default:
            width = preference.drawing.maxTextWidth
        }
      
        
        message = preference.drawing.message
        
        self.frame = CGRect(x: point.x - width / 2, y: point.y, width: width, height: preference.drawing.minHeight)
        self.backgroundColor = UIColor.clear
    }
    
    public func show() {
        
        self.computerTextSize()
        
        self.adjustFrame()
        
        self.contenLabel.text = message
        self.contenLabel.font = preference.drawing.font
        self.contenLabel.textColor = preference.drawing.textColor
        self.contenLabel.backgroundColor = preference.drawing.textBackGroundColor
        self.contenLabel.textAlignment = preference.drawing.textAlignment
        
        self.addSubview(contenLabel)
        
        UIApplication.shared.keyWindow?.addSubview(self)
        
        
        UIView.animate(withDuration: preference.animating.showDuration, delay: 0, options: .curveEaseIn, animations: { 
            self.transform = CGAffineTransform(scaleX: 1.05, y: 1.05)
        }) { (finished:Bool) in
            self.transform = CGAffineTransform.identity
        }
        
        if preference.animating.shouldDismiss {
             self.perform(#selector(self.dismiss), with: nil, afterDelay: self.preference.animating.delayDuration)
        }
       
    }
    
    func dismiss() {
        
        UIView.animate(withDuration: preference.animating.dismissDuration, delay: 0, options: .curveEaseInOut, animations: {
            self.alpha = 0
        }) { (finished:Bool) in
            self.removeFromSuperview()
        }

    }
    
    // MARK:-  Private
    
    private func computerTextSize() {
        
        let textInset:CGFloat = preference.drawing.textInset
        let attributes = [NSFontAttributeName : preference.drawing.font]
        
        var textSize = self.message.boundingRect(with: CGSize(width: preference.drawing.maxTextWidth, height: CGFloat.greatestFiniteMagnitude), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: attributes, context: nil).size
        
        textSize.width = ceil(textSize.width)
        textSize.height = ceil(textSize.height)
        
        let minHeight:CGFloat = preference.drawing.minHeight
        
        var retainMinHeight:CGFloat = 0
        var retainMaxHeight:CGFloat = 0
        
        switch preference.positioning.arrowPosition {
            
        case UIPopoverArrowDirection.up,UIPopoverArrowDirection.down:
            retainMinHeight = minHeight - arrowHeight - textInset * 2
            retainMaxHeight = preference.drawing.maxHeight - arrowHeight - textInset * 2
            break
        case UIPopoverArrowDirection.left,UIPopoverArrowDirection.right:
            retainMinHeight = minHeight - textInset * 2
            retainMaxHeight = preference.drawing.maxHeight - textInset * 2
            break
        default:
            break
            
        }
        
        if textSize.height < retainMinHeight {
            textSize.height = retainMinHeight
        }
        
        if  textSize.height > retainMaxHeight  {
            textSize.height = retainMaxHeight
        }
        
        var contentHeight:CGFloat = 0
        
        switch preference.positioning.arrowPosition {
            
        case UIPopoverArrowDirection.up,UIPopoverArrowDirection.down:
            contentHeight = textSize.height + arrowHeight + textInset * 2
            break
        case UIPopoverArrowDirection.left,UIPopoverArrowDirection.right:
            contentHeight = textSize.height + textInset * 2
            break
        default:
            break
            
        }
    
        if textSize.height > retainMinHeight && textSize.height < minHeight {
            contentHeight = minHeight
        }

        self.textSize = textSize
        
        self.contentSize = CGSize(width: width, height: contentHeight)
        
    }
    
    private func adjustFrame() {
        switch preference.positioning.arrowPosition {
            
        case UIPopoverArrowDirection.up,UIPopoverArrowDirection.down:
            adjustDirectionUpDown()
            
        case UIPopoverArrowDirection.left,UIPopoverArrowDirection.right:
            adjustDirectionLeftRight()
        default:
            break
        }
    }
    
    private func adjustDirectionUpDown() {
        var frameX:CGFloat = point.x - width / 2
        var frameY:CGFloat = point.y
        let textHInset:CGFloat = ceil(((contentSize.height - arrowHeight) - textSize.height) / 2)
        
        var contentY:CGFloat = textHInset
        
        if (point.x - width / 2) < 0 {
            frameX = 1
        }
        
        if (point.x + width / 2 > screenWidth) {
            frameX = screenWidth - width - 1
        }
        
        switch preference.positioning.arrowPosition {
        case UIPopoverArrowDirection.up:
            contentY = arrowHeight + textHInset
            break
        case UIPopoverArrowDirection.down:
            frameY = point.y - contentSize.height
            break
        default:
            break
        }
        
        self.contenLabel.frame = CGRect.init(x: preference.drawing.textInset, y: contentY, width: preference.drawing.maxTextWidth, height: textSize.height)
        
        self.frame = CGRect(x: frameX, y: frameY, width: width, height: contentSize.height)
    }
    
    private func adjustDirectionLeftRight() {
        var frameX:CGFloat = point.x
        var frameY:CGFloat = point.y - contentSize.height / 2
        
        var contentX:CGFloat = preference.drawing.textInset

        switch preference.positioning.arrowPosition {
        case UIPopoverArrowDirection.left:
            frameX = point.x
            contentX = arrowWidth +  preference.drawing.textInset
            break
        case UIPopoverArrowDirection.right:
            frameX = point.x - width
            contentX = preference.drawing.textInset
            break
        default:
            break
        }
        
        if (point.y - contentSize.height / 2) < 0 {
            frameY = 1
        }
        
        if (point.y + contentSize.height / 2 > screenHeight) {
            frameY = screenHeight - contentSize.height - 1
        }
        
        self.contenLabel.frame = CGRect.init(x:contentX, y: preference.drawing.textInset, width: preference.drawing.maxTextWidth, height: textSize.height)
        
        self.frame = CGRect(x: frameX, y: frameY, width: width, height: contentSize.height)
    }
    
    // MARK:- Override
    
    override func draw(_ rect: CGRect) {
        guard UIGraphicsGetCurrentContext() != nil else {
            return
        }

        let context = UIGraphicsGetCurrentContext()!
        context.saveGState()

        context.setFillColor(preference.drawing.backgroundColor.cgColor)
        context.setStrokeColor(preference.drawing.backgroundColor.cgColor)
        
        let contourPath = CGMutablePath()
        
        switch preference.positioning.arrowPosition {
            case UIPopoverArrowDirection.up:
              drawArrowUp(contourPath: contourPath)
            break
            case UIPopoverArrowDirection.down:
             drawArrowDown(contourPath: contourPath)
            break
            case UIPopoverArrowDirection.left:
             drawArrowLeft(contourPath: contourPath)
            break
            case UIPopoverArrowDirection.right:
            drawArrowRight(contourPath: contourPath)
            break
            
           default:
           break
        }
      
        context.addPath(contourPath)
        context.drawPath(using: CGPathDrawingMode.fillStroke)
        
        if preference.drawing.hasBorder {
            drawBorder(borderPath: contourPath, context: context)
        }
        
        context.restoreGState()
    }
    
    private func drawArrowUp(contourPath:CGMutablePath) {
        let height:CGFloat = contentSize.height
        let radius:CGFloat = preference.drawing.cornerRadius
        
        let beginX:CGFloat = point.x - self.frame.origin.x
        
        contourPath.move(to: CGPoint(x: beginX, y: 0))
        
        contourPath.addLine(to: CGPoint(x: beginX - arrowWidth / 2, y: arrowHeight))
        
        contourPath.addArc(tangent1End:CGPoint(x: 0, y: arrowHeight), tangent2End: CGPoint(x: 0, y:height), radius: radius)
        contourPath.addArc(tangent1End:CGPoint(x: 0, y: height), tangent2End: CGPoint(x: width, y: height), radius: radius)
        
        contourPath.addArc(tangent1End:CGPoint(x: width, y: height), tangent2End: CGPoint(x: width, y: arrowHeight), radius: radius)
        contourPath.addArc(tangent1End:CGPoint(x: width, y: arrowHeight), tangent2End: CGPoint(x: 0, y: arrowHeight), radius: radius)
        
        contourPath.addLine(to: CGPoint(x: beginX + arrowWidth / 2, y: arrowHeight))
        
        contourPath.addLine(to: CGPoint(x: beginX, y: 0))
    }
    
    private func drawArrowDown(contourPath:CGMutablePath) {
        let contentHeight:CGFloat = contentSize.height
        let radius:CGFloat = preference.drawing.cornerRadius
        
        let beginX:CGFloat = point.x - self.frame.origin.x
        
        let height:CGFloat = contentHeight - arrowHeight
        
        contourPath.move(to: CGPoint(x: beginX, y: contentHeight))
        
        contourPath.addLine(to: CGPoint(x: beginX - arrowWidth / 2, y: height))
        
        contourPath.addArc(tangent1End:CGPoint(x: 0, y: height), tangent2End: CGPoint(x: 0, y: 0), radius: radius)
        contourPath.addArc(tangent1End:CGPoint(x: 0, y: 0), tangent2End: CGPoint(x: width, y: 0), radius: radius)
        
        contourPath.addArc(tangent1End:CGPoint(x: width, y: 0), tangent2End: CGPoint(x: width, y: height), radius: radius)
        contourPath.addArc(tangent1End:CGPoint(x: width, y: height), tangent2End: CGPoint(x: 0, y: height), radius: radius)
        
        contourPath.addLine(to: CGPoint(x: beginX + arrowWidth / 2, y: height))
        
        contourPath.addLine(to: CGPoint(x: beginX, y: contentHeight))
    }
    
    private func drawArrowLeft(contourPath:CGMutablePath) {
        let contentHeight:CGFloat = contentSize.height
        let radius:CGFloat = preference.drawing.cornerRadius
        
        let beginY:CGFloat = point.y - self.frame.origin.y
        
        contourPath.move(to: CGPoint(x: 0, y: beginY))
        
        contourPath.addLine(to: CGPoint(x: arrowWidth, y: beginY - arrowHeight / 2))
        
        contourPath.addArc(tangent1End:CGPoint(x: arrowWidth, y: 0), tangent2End: CGPoint(x: width, y: 0), radius: radius)
        contourPath.addArc(tangent1End:CGPoint(x: width, y: 0), tangent2End: CGPoint(x: width, y: contentHeight), radius: radius)
        
        contourPath.addArc(tangent1End:CGPoint(x: width, y: contentHeight), tangent2End: CGPoint(x: arrowWidth, y: contentHeight), radius: radius)
        contourPath.addArc(tangent1End:CGPoint(x: arrowWidth, y: contentHeight), tangent2End: CGPoint(x: arrowWidth, y: 0), radius: radius)
        
        contourPath.addLine(to: CGPoint(x: arrowWidth, y: beginY + arrowHeight / 2))
        
        contourPath.addLine(to: CGPoint(x: 0, y: beginY))
    }
    
    private func drawArrowRight(contourPath:CGMutablePath) {
        let contentHeight:CGFloat = contentSize.height
        let radius:CGFloat = preference.drawing.cornerRadius
        
        let beginY:CGFloat = point.y - self.frame.origin.y
        
        
        let pathWidth:CGFloat = width - arrowWidth
        
        contourPath.move(to: CGPoint(x: width, y: beginY))
        
        contourPath.addLine(to: CGPoint(x: pathWidth, y: beginY - arrowHeight / 2))
        
        contourPath.addArc(tangent1End:CGPoint(x: pathWidth, y: 0), tangent2End: CGPoint(x: 0, y: 0), radius: radius)
        contourPath.addArc(tangent1End:CGPoint(x: 0, y: 0), tangent2End: CGPoint(x: 0, y: contentHeight), radius: radius)
        
        contourPath.addArc(tangent1End:CGPoint(x: 0, y: contentHeight), tangent2End: CGPoint(x: pathWidth, y: contentHeight), radius: radius)
        contourPath.addArc(tangent1End:CGPoint(x: pathWidth, y: contentHeight), tangent2End: CGPoint(x: pathWidth, y: 0), radius: radius)
        
        contourPath.addLine(to: CGPoint(x: pathWidth, y: beginY + arrowHeight / 2))
        
        contourPath.addLine(to: CGPoint(x: width, y: beginY))
    }
    
    private func drawBorder(borderPath: CGPath, context: CGContext) {
        context.addPath(borderPath)
        context.setStrokeColor(preference.drawing.borderColor.cgColor)
        context.setLineWidth(preference.drawing.borderWidth)
        context.strokePath()
    }
}

项目地址:FETipView-FlyElephant

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容