【iOS】MaskBtn图片蒙板按钮(swift)

在iOS开发中,用UIImage作为mask蒙板,实现一些有意思的效果,可以说很常见!本文记录我自己最近的项目中,使用UIImage反转后作为mask蒙板,结合UIBlurEffect毛玻璃特效,制作的按钮效果,给有类似需求的朋友作为参考!…… 请看下面的最终效果:


最终效果

如果你对这个感兴趣,那么接下来,我们就重新来做一遍!

制作思路:

  1. 使用CGBlendMode.destinationOut混合模式(反转图片内容)
  2. 将UIImage作为图层的mask蒙板
  3. 添加UIBlurEffect毛玻璃特效作为打底

具体思路如下图:


图片反转Mask

设置图层mask蒙版,直接设置UIview的mask属性就可以了:

 let bgLayer = UIView(frame: CGRect(origin: center, size: size))
 bgLayer.mask = mask // 设置mask属性

图片反转的方法,可以写成一个UIImage的扩展,方便调用,代码如下:

// 反转图片方法
func invert(size: CGSize, color: UIColor, padding: CGFloat, ratio: CGFloat) -> UIImage {
    let blendMode = CGBlendMode.destinationOut // 设置混合模式
    let drawRect = CGRect(origin: .zero, size: size) // 绘制区域
    
    // 定义图片上下文
    UIGraphicsBeginImageContextWithOptions(size, false, scale)
    /*======================================================*/
    color.setFill() // 填充颜色
    UIRectFill(drawRect) // 用指定颜色填充
    draw(in: drawRect.inset(by: UIEdgeInsets(top: padding, left: padding * ratio, bottom: padding, right: padding * ratio)), blendMode: blendMode, alpha: 1.0)
    let invertImage = UIGraphicsGetImageFromCurrentImageContext()!
    /*======================================================*/
    UIGraphicsEndImageContext() // 上下文结束
    
    return invertImage
}

为了使用方便,我是自定义了一个UIButton的子类,考虑到icon图标的长宽比例不同,所以暴露一个ratio比例和padding内边距的参数给外面进行设置。

背景图就随便,这里我用的是一个UIStackView里面放一些色块,主要是跟按钮有个对比,下面是完整代码:

  1. MaskBtn.swift (定义部分的代码)
//
//  MaskBtn.swift
//  UIKit-basic
//
//  Created by Qire_er on 2022/1/12.
//

import UIKit

class MaskBtn: UIButton {
    
    private var icon: UIImage! // icon图片
    private var size: CGSize! // icon尺寸
    private var padding: CGFloat! // icon内边距
    private var ratio: CGFloat! // icon宽高比例
    private var radius: CGFloat! // 圆角半径
    
    init(icon: UIImage, size: CGSize, padding: CGFloat, ratio: CGFloat, radius: CGFloat, tintColor: UIColor) {
        super.init(frame: .zero)
        self.icon = icon
        self.size = size
        self.padding = padding
        self.ratio = ratio
        self.radius = radius
        self.tintColor = tintColor
        createCompose()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // 创建图片蒙板效果
    private func createCompose() {
        let maskImg = icon.invert(size: CGSize(width: size.width, height: size.height), color: tintColor, padding: padding, ratio: ratio)
        let mask = UIImageView(image: maskImg)

        let bgLayer = UIView(frame: CGRect(origin: center, size: size))
        let blur = bgLayer.createBlurEffect(frame: bgLayer.frame)
        
        bgLayer.backgroundColor = tintColor
        bgLayer.mask = mask
        bgLayer.layer.cornerRadius = radius
        bgLayer.isUserInteractionEnabled = false    // 阻止响应用户交互,否则按钮事件无效
        blur.isUserInteractionEnabled = false   // 阻止响应用户交互,否则按钮事件无效
        blur.layer.cornerRadius = radius
        blur.clipsToBounds = true
        
        addSubview(blur)
        addSubview(bgLayer)
    }
}

///////////////////////////////////////////////////////////////////////////////////////////
// 偷懒一下,相关扩展就直接写到这里了!

extension UIImage {
    // 反转图片方法
    func invert(size: CGSize, color: UIColor, padding: CGFloat, ratio: CGFloat) -> UIImage {
        let blendMode = CGBlendMode.destinationOut // 设置混合模式
        let drawRect = CGRect(origin: .zero, size: size) // 绘制区域
        
        // 定义图片上下文
        UIGraphicsBeginImageContextWithOptions(size, false, scale)
        /*======================================================*/
        color.setFill() // 填充颜色
        UIRectFill(drawRect) // 用指定颜色填充
        draw(in: drawRect.inset(by: UIEdgeInsets(top: padding, left: padding * ratio, bottom: padding, right: padding * ratio)), blendMode: blendMode, alpha: 1.0)
        let invertImage = UIGraphicsGetImageFromCurrentImageContext()!
        /*======================================================*/
        UIGraphicsEndImageContext() // 上下文结束
        
        return invertImage
    }
}

///////////////////////////////////////////////////////////////////////////////////////////

extension UIView {
    // 创建【毛玻璃】效果
    func createBlurEffect(frame: CGRect) -> UIVisualEffectView {
        let blurEffect = UIBlurEffect(style: .regular)
        let blurView = UIVisualEffectView(effect: blurEffect)
        blurView.frame = frame
        return blurView
    }
}

///////////////////////////////////////////////////////////////////////////////////////////
  1. MaskBtnVC.swift(调用部分的代码)
//
//  MaskBtnVC.swift
//  UIKit-basic
//
//  Created by Qire_er on 2022/1/12.
//

import UIKit

class MaskBtnVC: UIViewController {
    
    // 图片相关配置信息
    // 这里直接用系统图标名称作为key,用ratio比例作为value
    // 另外,size不同,ratio也不一样,暂时没有想出什么更方便的方法!
    private let imgNames = ["heart.fill": 0.75, "camera.fill": 0.65, "book.fill": 0.75, "gearshape.fill": 0.95, "alarm.fill": 1.0, "gift.fill": 0.75]

    override func viewDidLoad() {
        super.viewDidLoad()
        makeBG() // 创建背景图
        makeBtns() // 创建按钮
        view.backgroundColor = .white
    }
    
    // 创建按钮方法
    private func makeBtns() {
        let btnStack = UIStackView()
        btnStack.translatesAutoresizingMaskIntoConstraints = false
        btnStack.axis = .vertical
        btnStack.alignment = .center
        btnStack.distribution = .fillEqually
        
        for item in imgNames {
            let btn = MaskBtn(
                icon: UIImage(systemName: item.key)!,
                size: CGSize(width: 100, height: 100),
                padding: 20,
                ratio: CGFloat(item.value),
                radius: 32,
                tintColor: .white.withAlphaComponent(0.85)
            )
            // 加一点阴影效果
            btn.layer.shadowColor = UIColor.black.cgColor
            btn.layer.shadowOpacity = 0.15
            btn.layer.shadowOffset = CGSize(width: 5, height: 5)
            btn.layer.shadowRadius = 10
            
            btnStack.addArrangedSubview(btn)
            
            btn.widthAnchor.constraint(equalToConstant: 100).isActive = true
        }
        view.addSubview(btnStack)
        
        // 添加约束
        NSLayoutConstraint.activate([
            btnStack.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 15),
            btnStack.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -15),
            btnStack.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 15),
            btnStack.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -5),
        ])
        
    }
    
    // 创建背景图方法
    private func makeBG() {
        let colorStack = UIStackView()
        colorStack.translatesAutoresizingMaskIntoConstraints = false
        colorStack.axis = .vertical
        colorStack.distribution = .fillEqually
        
        // 创建色块
        for i in 0..<30 {
            let colorBlock = UIView()
            colorBlock.backgroundColor = UIColor(
                hue: CGFloat(i * 5 + 16) / 100.0,
                saturation: CGFloat((i * 15) % 100) / 100.0,
                brightness: 0.85,
                alpha: 1.0
            )
            colorStack.addArrangedSubview(colorBlock)
        }
        view.addSubview(colorStack)
        
        // 添加约束
        NSLayoutConstraint.activate([
            colorStack.leftAnchor.constraint(equalTo: view.leftAnchor),
            colorStack.rightAnchor.constraint(equalTo: view.rightAnchor),
            colorStack.topAnchor.constraint(equalTo: view.topAnchor),
            colorStack.bottomAnchor.constraint(equalTo: view.bottomAnchor),
        ])
    }

}

(==完==)

ps: 以上仅代表个人浅见,如果你有什么高见,也欢迎讨论交流!-

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

推荐阅读更多精彩内容