UITextView link跳转

废话不多说,直接上代码,通过一个弹窗来展示效果

import UIKit
import FZBaseKit
import SnapKit
import RxSwift
@objcMembers
/// 新的弹窗alert,支持富文本点击跳转,支持左侧取消按钮,右侧确定按钮;支持中间一个确定按钮
open class FZAlertView: UIView {
    //按钮点击回调,取消按钮false 确认按钮 true
    typealias hander = (_ result: Bool)->()
    typealias strHander = (_ index: Int, _ linkStr: String)->()
    //按钮点击回调属性
    private var callBlock:hander?
    //富文本点击回调属性
    private var strCallBlock:strHander?
    private var attributedArr: Array<FZAlertViewModel>?
    private let bag = DisposeBag.init()
    //白色承载体
    lazy var bgView: UIView = {
        let view = UIView.init()
        view.backgroundColor = .white
        view.layer.cornerRadius = CGFloat(KRatio(15))
        view.clipsToBounds = true
        return view
    }()
    
    lazy var contentTextView: UITextView = {
        let textView = UITextView.init()
        textView.textAlignment = .center
        textView.font = KFont(CGFloat(KRatio(15)))
        textView.textColor = UIColor.hex("#3D3838")
        textView.backgroundColor = .white
        textView.delegate = self
        textView.isEditable = false //禁止编辑
        textView.isScrollEnabled = false //禁止滚动
        return textView
    }()
    
    lazy var cancelBtn: UIButton = {
        let btn = UIButton.initButton(title: "", titleColor: UIColor.hex("#3D3838"), fontSize: 18, bgColor: UIColor.clear, imageName: "")
        btn.layer.masksToBounds = true
        btn.layer.cornerRadius = CGFloat(KRatio(22))
        btn.layer.borderWidth = 0.5
        btn.layer.borderColor = UIColor.hex("#918B8B").cgColor
        return btn
    }()
    
    lazy var confirmBtn: UIButton = {
        let btn = UIButton.initButton(title: "", titleColor: UIColor.white, fontSize: 18, bgColor: UIColor.hex("#6249EE"), imageName: "")
        btn.layer.masksToBounds = true
        btn.layer.cornerRadius = CGFloat(KRatio(22))
        return btn
    }()
    
    /// 创建alertView
    /// - Parameters:
    ///   - message: 文本信息
    ///   - sureBtnTitle: 确认按钮标题
    ///   - cancelBtnTitle: 取消按钮标题,如果无取消按钮该值不传
    ///   - supperVC: 推出alertview的VC,如果为空,就通过KAppwindow弹出
    ///   - btnActionCallBack: 取消、确认按钮点击回调
    ///   - attributedArr: 富文本点击数组,数组内部字典组成为,无富文本点击可不传
    ///   - attributedClickActionCallBack: 富文本点击回调
    /// - Returns: 返回alertView
   public static func showAlertView(message: String, supperVC: UIViewController?, sureBtnTitle: String, cancelBtnTitle: String = "", attributedArr: Array<FZAlertViewModel> = [], btnActionCallBack: @escaping (_ result: Bool)->(), attributedClickActionCallBack: @escaping (_ index: Int, _ linkStr: String)->()) {
        let frame = supperVC == nil ? CGRect.init(x: 0, y: 0, width: KWIDTH_SCREEN, height: KHEIGHT_SCREEN) : supperVC!.view.bounds
        let alertView = FZAlertView.init(frame: frame)
        alertView.callBlock = btnActionCallBack
        alertView.strCallBlock = attributedClickActionCallBack
        alertView.attributedArr = attributedArr
        alertView.initUI(supperVC: supperVC)
        alertView.setData(message: message, sureBtnTitle: sureBtnTitle, cancelTitle: cancelBtnTitle)
        alertView.layoutUI(message: message, cancelTitle: cancelBtnTitle)
    }
    
    //UI构建
    private func initUI(supperVC: UIViewController?) {
        self.backgroundColor = UIColor.RGBA(0, 0, 0, 0.6)
        self.addSubview(bgView)
        bgView.addSubview(contentTextView)
        bgView.addSubview(confirmBtn)
        bgView.addSubview(cancelBtn)
        if supperVC == nil {
            KAppWindow?.addSubview(self)
        } else {
            supperVC?.view.addSubview(self)
            supperVC?.view.bringSubviewToFront(self)
        }
    }
    
    //数据处理
    private func setData(message: String, sureBtnTitle: String, cancelTitle: String) {
        self.confirmBtn.setTitle(sureBtnTitle, for: .normal)
        self.cancelBtn.setTitle(cancelTitle, for: .normal)
        //textView富文本
        contentTextView.attributedText = self.getContentTextViewAttributedText(message: message)
        //按钮点击
        cancelBtn.rx.tap.asObservable()
            .subscribe {[weak self] _ in
                guard let self = self else {return}
                if self.callBlock != nil {
                    self.callBlock!(false)
                    self.removeFromSuperview()
                }
            }
            .disposed(by: bag)
        
        confirmBtn.rx.tap.asObservable()
            .subscribe{[weak self] _ in
                guard let self = self else {return}
                if self.callBlock != nil {
                    self.callBlock!(true)
                    self.removeFromSuperview()
                }
            }
            .disposed(by: bag)
    }
    
    //布局构建
    private func layoutUI(message: String, cancelTitle: String) {
        let btnWidth = KRatio(105)
        let btnHeight = KRatio(44)
        var contentWidth = KRatio(275)//默认高度
        let textViewHeight = message.getStringHeight(CGFloat(KRatio(230)), KFont(CGFloat(KRatio(15)))) + CGFloat(KRatio(20))
        
        var contentHeight = Int(textViewHeight) + KRatio(20) + btnHeight + KRatio(40)
        if contentHeight < KRatio(175) {
            contentHeight = KRatio(175)
        }
        if contentHeight > KRatio(550) {
            //如果高度过高,那么设置最大高度,并且设置textView可滚动
            contentHeight = KRatio(550)
            contentTextView.isScrollEnabled = true
        }
        
        bgView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.size.equalTo(CGSize.init(width: contentWidth, height: contentHeight))
        }
        contentTextView.snp_makeConstraints { make in
            make.left.equalTo(bgView).offset(KRatio(20))
            make.right.equalTo(bgView).offset(KRatio(-20))
            make.top.equalTo(bgView).offset(KRatio(20))
        }
        //按钮布局分为取消、确认按钮和只有确认按钮两种情况,根据cancelTitle来判断
        if cancelTitle.count == 0 {
            //隐藏cancelTitle
            cancelBtn.isHidden = true
            //surebtn据中
            confirmBtn.snp.makeConstraints { make in
                make.centerX.equalTo(bgView)
                make.size.equalTo(CGSize.init(width: btnWidth, height: btnHeight))
                make.bottom.equalTo(bgView).offset(KRatio(-17))
            }
        } else {
            cancelBtn.snp.makeConstraints { make in
                make.left.equalTo(bgView).offset(KRatio(20))
                make.bottom.equalTo(bgView).offset(KRatio(-17))
                make.size.equalTo(CGSize.init(width: btnWidth, height: btnHeight))
            }
            confirmBtn.snp.makeConstraints { make in
                make.bottom.size.equalTo(cancelBtn)
                make.right.equalTo(bgView).offset(KRatio(-20))
            }
        }
    }
}

extension FZAlertView: UITextViewDelegate {
    //对link富文本点击进行处理
    public func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
        KLog("scheme = \(URL.scheme?.description) --- range = \(characterRange.description) --- inter = \(interaction)")
        for (index, model) in self.attributedArr!.enumerated() {
            if URL.absoluteString.contains(model.linkStr) {
                //如果url包含model中的linkUrl,那么说明点击的是该link跳转,那么将index回传回去
                if self.strCallBlock != nil {
                    self.strCallBlock!(index, model.linkStr)
                }
                return false
            }
        }
        return true
    }
}

extension FZAlertView {
    //获取富文本
    private func getContentTextViewAttributedText(message: String) -> NSAttributedString {
        let attrStr = NSMutableAttributedString.init(string: message)
        attrStr.addAttribute(NSAttributedString.Key.font, value: KFont(CGFloat(KRatio(15))), range: NSRange.init(location: 0, length: message.count))
        if self.attributedArr!.count > 0 {
            for model in attributedArr! {
                //根据数组添加字体、颜色、link富文本
                //跳转链接如果不是http或https链接跳转,那么就在后面追加://
                attrStr.addAttributes([NSAttributedString.Key.font: KFont(CGFloat(KRatio(16))), NSAttributedString.Key.foregroundColor: model.linkColor, NSAttributedString.Key.link: model.linkStr.contains("http") ? model.linkStr : model.linkStr+"://"], range: model.linkRange)
            }
        }
        return attrStr
    }
}

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

推荐阅读更多精彩内容