Swift-消息提示框

App中經(jīng)常會(huì)看到各種各樣的消息提示框停局,如下圖消息提示一定遇到過:


FlyElephant.png

消息提示框經(jīng)常需要設(shè)置的很钓,文本框?qū)挾龋畲蟾叨榷裕^指示的高度和寬度码倦,背景顏色,文字顏色锭碳,動(dòng)畫袁稽,動(dòng)畫時(shí)間,消失時(shí)間:

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 = "《道德經(jīng)》是春秋時(shí)期老子(李耳)的哲學(xué)作品擒抛,又稱《道德真經(jīng)》推汽、《老子》、《五千言》歧沪、《老子五千文》-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 = "《顏氏家訓(xùn)》是漢民族歷史上第一部?jī)?nèi)容豐富歹撒,體系宏大的家訓(xùn),也是一部學(xué)術(shù)著作诊胞。作者顏之推暖夭,是南北朝時(shí)期著名的文學(xué)家、教育家撵孤。-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 = "理想國(guó)-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年于《小說野性時(shí)代》連載,于2012年3月由角川書店發(fā)行單行本邪码。該書講述了在僻靜街道旁的一家雜貨店裕菠,只要寫下煩惱投進(jìn)店前門卷簾門的投信口,第二天就會(huì)在店后的牛奶箱里得到回答:因男友身患絕癥闭专,年輕女孩月兔在愛情與夢(mèng)想間徘徊奴潘;松岡克郎為了音樂夢(mèng)想離家漂泊旧烧,卻在現(xiàn)實(shí)中寸步難行;少年浩介面臨家庭巨變萤彩,掙扎在親情與未來的迷茫中……他們將困惑寫成信投進(jìn)雜貨店粪滤,奇妙的事情隨即不斷發(fā)生"
        
        let tipView:FETipView = FETipView(preferences: preference)
        tipView.show()
    }

完整實(shí)現(xiàn)代碼:

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()
    }
}

項(xiàng)目地址:FETipView-FlyElephant

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末斧拍,一起剝皮案震驚了整個(gè)濱河市雀扶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌肆汹,老刑警劉巖愚墓,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異昂勉,居然都是意外死亡浪册,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門岗照,熙熙樓的掌柜王于貴愁眉苦臉地迎上來村象,“玉大人,你說我怎么就攤上這事攒至『裾撸” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵迫吐,是天一觀的道長(zhǎng)库菲。 經(jīng)常有香客問我,道長(zhǎng)志膀,這世上最難降的妖魔是什么熙宇? 我笑而不...
    開封第一講書人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮溉浙,結(jié)果婚禮上烫止,老公的妹妹穿的比我還像新娘。我一直安慰自己戳稽,他們只是感情好烈拒,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著广鳍,像睡著了一般荆几。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上赊时,一...
    開封第一講書人閱讀 51,443評(píng)論 1 302
  • 那天吨铸,我揣著相機(jī)與錄音,去河邊找鬼祖秒。 笑死诞吱,一個(gè)胖子當(dāng)著我的面吹牛舟奠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播房维,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼沼瘫,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了咙俩?” 一聲冷哼從身側(cè)響起耿戚,我...
    開封第一講書人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎阿趁,沒想到半個(gè)月后膜蛔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡脖阵,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年皂股,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片命黔。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡呜呐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出悍募,到底是詐尸還是另有隱情蘑辑,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布搜立,位于F島的核電站以躯,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏啄踊。R本人自食惡果不足惜忧设,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望颠通。 院中可真熱鬧址晕,春花似錦、人聲如沸顿锰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽硼控。三九已至刘陶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間牢撼,已是汗流浹背匙隔。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留熏版,地道東北人纷责。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓捍掺,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親再膳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子挺勿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容