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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

友情链接更多精彩内容