Swift 实现全屏水印的参考

本文主要讨论的是给图片或者视图添加全屏水印。比较常见的是添加单个水印,这个比较好处理,网络上也有很多参考的方法。本文实现的是铺满的全屏水印,具体参考效果如下:

实现思路:

1、根据水印文本以及相应样式生成水印图片,水印图大小根据文本计算而来

2、生成需要铺满水印图片或视图 的空白底图

3、采用贴地砖的方式将水印图从左至右,从上至下贴在底图上

具体实现参考代码如下:

/// 创建全铺图片水印
    /// - Parameters:
    ///   - strTxt: 水印文本
    ///   - fsize: 全铺的尺寸
    ///   - corners: 圆角信息(可选)
    ///   - r: 圆角值(可选)
    /// - Returns: UIImage
    func createWatermarkFor(Text strTxt:String,
                            andFullSize fsize:CGSize,
                            andCorners corners:UIRectCorner? = nil,
                            withRadius r:CGFloat? = nil) -> UIImage {
        
        //[S] 1、设置水印样式
        let paragraphStyle:NSMutableParagraphStyle = NSMutableParagraphStyle()
        paragraphStyle.lineBreakMode = .byWordWrapping
        paragraphStyle.lineSpacing = 6.0 * Setting.shareInstance.K_APP_PROPORTION
        paragraphStyle.alignment = .center
        
        var _attr:[NSAttributedString.Key:Any] = [
            .font : UIFont.systemFont(ofSize: 15, weight: .semibold),
            .foregroundColor:UIColor.init().colorFromHexInt(hex: 0xE8E8E8, alpha: 0.5),
            .paragraphStyle: paragraphStyle,
            .kern:1.0,
        ]
        
        if #available(iOS 14.0, *) {
            _attr[.tracking] = 1.0
        }
        
        let attributedString:NSMutableAttributedString = NSMutableAttributedString.init(string: strTxt)
        let stringRange = NSMakeRange(0, attributedString.string.utf16.count)
        attributedString.addAttributes(_attr,range: stringRange)
        //[E]
        
        //[S] 2、建立水印图
        let _max_value = attributedString.size().width > attributedString.size().height ? attributedString.size().width : attributedString.size().height
        let _size = CGSize.init(width: _max_value + 10, height: _max_value + 10)
        
        //2.1、设置上下文
        if UIScreen.main.scale > 1.5 {
            UIGraphicsBeginImageContextWithOptions(_size,false,0)
        }
        else{
            UIGraphicsBeginImageContext(_size)
        }
        var context = UIGraphicsGetCurrentContext()
        
        //2.2、根据中心开启旋转上下文矩阵
        //将绘制原点(0,0)调整到源image的中心
        context?.concatenate(.init(translationX: _size.width * 0.8, y: _size.height * 0.4))
        
        //以绘制原点为中心旋转45°
        context?.concatenate(.init(rotationAngle: -0.25 * .pi))
        
        //将绘制原点恢复初始值,保证context中心点和image中心点处在一个点(当前context已经发生旋转,绘制出的任何layer都是倾斜的)
        context?.concatenate(.init(translationX: -_size.width * 0.8, y: -_size.height * 0.4))
        
        //2.3、添加水印文本
        attributedString.draw(in: .init(origin: .zero, size: _size))
        
        //2.4、从上下文中获取水印图
        let _waterImg = UIGraphicsGetImageFromCurrentImageContext() ?? UIImage.init()
        //[E]
        
        //[S] 3、重设上下文,建立底图
        if UIScreen.main.scale > 1.5 {
            UIGraphicsBeginImageContextWithOptions(fsize,false,0)
        }
        else{
            UIGraphicsBeginImageContext(fsize)
        }
        context = UIGraphicsGetCurrentContext()
        
        //3.1圆角底图(可选)
        if corners != nil && r != nil && r?.isNaN == false && r?.isFinite != false {
            let rect:CGRect = .init(origin: .zero, size: fsize)
            let bezierPath:UIBezierPath = UIBezierPath.init(roundedRect: rect,
                                                            byRoundingCorners: corners!,
                                                            cornerRadii: CGSize(width: r!, height: r!))
            
            context?.addPath(bezierPath.cgPath)
        }
        
        //3.2 将水印图贴上去
        var _tempC = fsize.width / _waterImg.size.width
        var _maxColumn:Int = _tempC.isNaN || !_tempC.isFinite ? 1 : Int(_tempC)
        if fsize.width.truncatingRemainder(dividingBy: _waterImg.size.width) != 0 {
            _maxColumn += 1
        }
        
        _tempC = fsize.height / _waterImg.size.height
        var _maxRows:Int = _tempC.isNaN || !_tempC.isFinite ? 1 : Int(_tempC)
        if fsize.height.truncatingRemainder(dividingBy: _waterImg.size.height) != 0 {
            _maxRows += 1
        }
        
        for r in 0..<_maxRows {
            for c in 0..<_maxColumn {
                let _rect:CGRect = .init(origin: .init(x: CGFloat(c) * _waterImg.size.width,
                                                       y: CGFloat(r) * _waterImg.size.height),
                                         size: _waterImg.size)
                _waterImg.draw(in: _rect)
            }
        }
        
        //裁剪、透明
        context?.clip()
        context?.setFillColor(UIColor.clear.cgColor)
        context?.fill(.init(origin: .zero, size: fsize))
        
        //3.3 输出最终图形
        let _canvasImg = UIGraphicsGetImageFromCurrentImageContext() ?? UIImage.init()
        //[E]
        
        //4、关闭图形上下文
        UIGraphicsEndImageContext()
        
        return _canvasImg
    }

调用方法示例:

let _img = createWatermarkFor(Text:"      水印文本1\n 水印文本2\n水印文本3", andFullSize:_imgV.size)

思考:

1、水印图需要旋转可参考旋转方向示例图:

 由于旋转的是文本,水印图本身并未旋转,因此可能导致文本会超出水印图的区域而被裁剪,导致未显示全。

解决方案:

1、设置水印图的尺寸为水印文本计算而来的宽或高的最大值为其宽度和高度,这样确保旋转后内容不会超出水印(这只是针对水印文本居中对齐的情况)

2、假如水印文本要求左/右对齐,除了采用方案1,还需要在文本开头添加空白,让其避开可能被裁掉的区域(尤其是一个水印图中要设置多行文本的情况下)

其他细节描述,请参考代码注释,有好的其他方案欢迎交流。

本文结束

猜你喜欢

转载自blog.csdn.net/yimiyuangguang/article/details/121852504