swift 可拖拽的浮标

import UIKit

import RxSwift

import RxCocoa

import YPSVG

 

/// 关联的拖拽浮标的key

fileprivate var DragBuoyKey = "DragBuoyKey"

fileprivate var DelBuoyKey = "DelBuoyKey"

fileprivate var HiddenBuoyKey = "HiddenBuoyKey"

 

 

extension UIViewController {

    /// 拖拽类型

    enum DragBuoyType: String {

        /// 首页(招工大列表)

        case MAIN_FIND_WORKER_LIST = "MAIN_FIND_WORKER_LIST"

        /// 招工详情页

        case FIND_WORKER_DETAIL = "FIND_WORKER_DETAIL"

        /// 我的招工详情

        case MY_FIND_WORKER_DETAIL = "MY_FIND_WORKER_DETAIL"

        /// 我的招工列表

        case MY_FIND_WORKER_LIST = "MY_FIND_WORKER_LIST"

        /// 找活列表页

        case MAIN_MY_FIND_JOB_LIST = "MAIN_MY_FIND_JOB_LIST"

        /// 找活详情页

        case FIND_JOB_DETAIL = "FIND_JOB_DETAIL"

        /// 我的找活名片

        case MY_RESUME = "MY_RESUME"

        /// 消息

        case MAIN_MESSAGES = "MAIN_MESSAGES"

        /// 会员中心

        case MAIN_USER_CENTER = "MAIN_USER_CENTER"

        /// 关于鱼泡

        case aboutAppForFaceBook = "ABOUT_APP_FOR_FACEBOOK"

    }

    /// 动态关联一个DisposeBag 每次滚动的时候会取消定时器

    private var hiddenBuoy: DisposeBag{

        set{

            objc_setAssociatedObject(self, &HiddenBuoyKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)

        }

        get{

            if let items = objc_getAssociatedObject(self, &HiddenBuoyKey) as? DisposeBag{

                return items

            }else{

                let tmp = DisposeBag()

                objc_setAssociatedObject(self, &HiddenBuoyKey, tmp, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)

                return tmp

            }

        }

    }

    

    /// 动态管理一个添加的数组

    fileprivate var showItems: NSMutableArray{

        if let items = objc_getAssociatedObject(self, &DragBuoyKey) as? NSMutableArray{

            return items

        }

        let tmp : NSMutableArray = .init()

        objc_setAssociatedObject(self, &DragBuoyKey, tmp, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)

        return tmp

    }

    fileprivate var delItems: NSMutableArray{

        if let items = objc_getAssociatedObject(self, &DelBuoyKey) as? NSMutableArray{

            return items

        }

        let tmp : NSMutableArray = .init()

        objc_setAssociatedObject(self, &DelBuoyKey, tmp, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)

        return tmp

    }

    

    /// 获取浮标数据

    /// - Parameters:

    ///   - entranceString: 入口url -entranceString后台配置的一致

    ///   - isIndex: isIndex在首页 1便是 0-不是

    ///   - jobId:弹窗的-jobId

    ///   - isReviewed: 找活名片是不是审核通过了 - 需要单独处理的

    func getBuoyData(entranceString: DragBuoyType,isIndex: Int = 0,jobId: String = "",isReviewed: Bool = true){

        if IsActive(){ // 有的浮标不通过线上审核也需要显示的

            YPGeneralInterface.getBuoy(entrance: entranceString.rawValue,isIndex: isIndex).request(model: YPResponseModel<YPBuoyArrayModel>.self).bind{ [weak self] res in

                guard let weakSelf = self else {return}

                if res.isOk {

                    if let model = res.data?.lists {

                        var delArray: [Int: YPBuoyListModel] = [:]

                        model.forEach { buoyModel in

                            if let firstModel = buoyModel.list?.first {

                                if firstModel.landingPageUrl == YPNavigationPath.Resume.cardPreview.rawValue.navigatorPath && isReviewed == false {

                                    return

                                }

                                firstModel.homeIndex = isIndex

                                firstModel.jobId = jobId

                                delArray[buoyModel.position ?? 1] = firstModel

                            }

                        }

                        weakSelf.buoyAdditem(listArray: delArray)

                    }

                }

            }.disposed(by: rx.disposeBag)

        }

    }

    

    /// 创建浮标

    private func buoyAdditem(listArray:[Int: YPBuoyListModel]){

        

        /// 移除items

        showItems.forEach { item in

            (item as? YPBuoyView)?.removeFromSuperview()

        }

        showItems.removeAllObjects()

        

        listArray.forEach { position,listModel in

            let id = listModel.id ?? 0

            if !YPBuoyViewManager.manager.addBuoyView(vc: self, id: id) { return }

            

            /// 添加浮标view

            let view = YPBuoyView(model: listModel)

            

            if let rect = YPBuoyViewManager.manager.getBuoyRect(vc: self, id: id){

                view.frame = rect

            }else{

                var tmpHeight = YPScreen.screen_h - YPScreen.safe_bottom

                if let controller = UIViewController.getCurrent, controller.navigationController?.viewControllers.count == 1{

                    tmpHeight -= 49

                }

                //距离底部100

                tmpHeight -= 90.scale

                if listModel.entranceUrl == DragBuoyType.FIND_WORKER_DETAIL.rawValue || listModel.entranceUrl == DragBuoyType.MY_FIND_WORKER_DETAIL.rawValue {/// 如果是招工详情界面需要留出置顶按钮位置

                    tmpHeight -= 55.scale

                }else if listModel.entranceUrl == DragBuoyType.MAIN_FIND_WORKER_LIST.rawValue {

                    tmpHeight += 30.scale

                }

                /// 间距

                let offest: CGFloat = 8.scale

                let num: CGFloat = 3-CGFloat(position)

                let viewH: CGFloat = view.mj_h+offest

                let positionH: CGFloat =  num * viewH

                let totalH = tmpHeight-TabBarHeight-positionH

                view.mj_y = totalH

//                    - 80.scale

                /// 根据id-保存frame位置

                YPBuoyViewManager.manager.addBuoyRect(vc: self, id: id, rect: view.frame)

            }

            

            view.closeButton.rx.tap().driverJustComplete {[weak self] _ in

                guard let weakSelf = self else {return}

                weakSelf.closeAct(view)

            }.disposed(by: view.rx.disposeBag)

            

            //初始化点击手势

            let tap = UITapGestureRecognizer.init()

            //绑定

            tap.rx.event.filter{$0.isEnabled}.debounce(.microseconds(500), scheduler: MainScheduler.asyncInstance).bind{[weak self]_ in

                guard let weakSelf = self else {return}

                weakSelf.iconClick(listModel: listModel,position: position)

            }.disposed(by: rx.disposeBag)

            //添加点击手势

            view.iconView.addGestureRecognizer(tap)

            

            showItems.add(view)

            self.view.addSubview(view)

            

        }

    }

    

    /// 关闭按钮的点击事件

    @objc private func closeAct(_ sender : YPBuoyView){

        showItems.remove(sender)

        sender.removeFromSuperview()

        delItems.add(sender.model.type ?? 0)

        YPBuoyViewManager.manager.delete(vc: self, id: sender.model.id ?? 0)

    }

    

    /// 浮标的点击事件

    func iconClick(listModel: YPBuoyListModel,position: Int){

        if listModel.type == 1 { ///  1-普通类型 有三种情况的处理

            nomalCommonType(listModel: listModel)

        }else if listModel.type == 2 { /// 2-大转盘类型 -直接跳大转盘

            didPlaylucky()

        }else if listModel.type == 3 { /// 3-分享类型

            bouncedProcessing(listModel: listModel) //  normal - text - photo

        }else if listModel.type == 4 { /// 4-首页-分享送积分类型

            getIntegarl(homeIndex: listModel.homeIndex ?? 1)

            

        }else if listModel.type == 5{ /// 免费送好礼

            navigatorEx?.push(YPNavigationPath.Member.Gift.rawValue.navigatorPath)

        }else{ /// 最后其他的处理为-普通类型-

            nomalCommonType(listModel: listModel)

        }

    }

    

    // 1-普通类型处理 -分三种情况

    func nomalCommonType(listModel: YPBuoyListModel){

        if listModel.jumpType == 1 {//1-跳转H5页面

            let webVC = BaseWebViewController(navigationType: .blueStyle)

            webVC.title = listModel.title

            webVC.fullURL = listModel.landingPageUrl

            navigationController?.pushViewController(webVC, animated: true)

        }else if listModel.jumpType == 2 { // 2-跳转内部URL

            /// 是小说的跳转需要做点击的统计-需单独处理

            if listModel.landingPageUrl == YPNavigationPath.Member.story.rawValue.navigatorPath {

                YPGeneralInterface.novelStatistical(btnIDType: "100004").request(model: YPResponseModel<String>.self).bind{ res in

                    print(res)

                }.disposed(by: rx.disposeBag)

            }

            navigatorEx?.push("\(listModel.landingPageUrl ?? "")")

            

        }else if listModel.jumpType == 3{ // 3-跳转小程序

            if WXApi.isWXAppInstalled() {

                let req = WXLaunchMiniProgramReq()

                /// 拉起的小程序的username-原始ID

                req.userName = listModel.mini ?? ""

                /// 拉起小程序页面的可带参路径,不填默认拉起小程序首页

                req.path = listModel.landingPageUrl

                switch GetBASEURL() {

                case BaseUrlTest: // 测试站

                    req.miniProgramType = .preview

                case BaseUrlPre: // 预发布

                    req.miniProgramType = .preview

                case BaseUrlReleasePre:/// 预发布正式站

                    req.miniProgramType = .preview

                case BaseUrlRelease:// 正式站

                    req.miniProgramType = .release

                default:

                    break

                }

                WXApi.send(req) { _ in }

            }

            

        }

        

    }

    /// 弹框处理

    func bouncedProcessing(listModel: YPBuoyListModel){

        

        let page = "\(self.classForCoder)"

        if listModel.entranceUrl == DragBuoyType.FIND_WORKER_DETAIL.rawValue { /// 如果别人的是招工详情页

            let jobId: Int = Int(listModel.jobId ?? "0") ?? 0

            YPShareEntCustorm.init(page, .broadside, .job, jobId).doshare()

        }else if listModel.entranceUrl == DragBuoyType.MY_FIND_WORKER_DETAIL.rawValue { // 分享到群的弹框

            let jobId: Int = Int(listModel.jobId ?? "0") ?? 0

            YPShareEntCustorm.init(page, .broadsideme, .job, jobId).doshare()

        }else {

            

            if let resumeVc = self as? YPResumeDetailVC {

                let detailId: Int = Int(resumeVc.resumeID ?? "0") ?? 0

                YPShareEntCustorm.init(page, .buoyShare, .resume, detailId).doshare()

            }else if let recruitVc = self as? RecruitDetailVC{

                let detailId: Int = recruitVc.detailDataM.value?.id ?? 0

                YPShareEntCustorm.init(page, .buoyShare, .job, detailId).doshare()

            }else{

                YPShareEntCustorm.init(page, .buoyShare).doshare()

            }

        }

    }

    

    /// 首页的分享赚积分逻辑

    func getIntegarl(homeIndex: Int) {

        let share = YPShareEntCustorm.init("\(self.classForCoder)", .buoyShareGetIntegral)

        share.willShare = {[weak self] model in

            guard let weakSelf = self else { return }

            let turntableType = model.turntableType ?? "2"

            return YPIndexAPI.queryIncentiveShare.request(model: YPCodeMsgRespModel<String>.self).filter{$0.isOk}.flatMap{ _ -> Observable<FastResumeAlertView.Action> in

                let msg = NSMutableAttributedString()

                let left: NSMutableAttributedString?

                let right: NSMutableAttributedString?

                if turntableType == "2" {

                    msg.append("获得".axc_attributedStr(color: "#333333", font: 14))

                    msg.append("1".axc_attributedStr(color: UIColor.red, font: 14))

                    msg.append("临时积分。\n玩大转盘免费获得更多积分,去赚积分?".axc_attributedStr(color: "#333333", font: 14))

                    left = "知道了".axc_attributedStr(color: ColorLightGray, font: 14)

                    right = "去玩大转盘".axc_attributedStr(color: "#333333", font: 14)

                }else {

                    msg.append("获得".axc_attributedStr(color: "#333333", font: 14))

                    msg.append("1".axc_attributedStr(color: UIColor.red, font: 14))

                    msg.append("临时积分".axc_attributedStr(color: "#333333", font: 14))

                    left = "知道了".axc_attributedStr(color: ColorLightGray, font: 14)

                    right = nil

                }

                return FastResumeAlertView.show(title: "分享成功", msg: msg, left: left, right: right)

            }.bind{[weak self] action in

                guard let weakSelf = self else {return}

                // 重新请求下判断是哪个类型的浮标 homeIndex:1-首页

                weakSelf.getBuoyData(entranceString: .MAIN_FIND_WORKER_LIST, isIndex: homeIndex)

                //跳转前 请求一次分享统计

                YPReportAPI.reportShareAction(type: "7").request.subscribe{ _ in }.disposed(by: weakSelf.rx.disposeBag)

                //点击右键玩大转盘

                if action == .right{

                    weakSelf.didPlaylucky()

                }

            }.disposed(by: weakSelf.rx.disposeBag)

        }

        share.doshare()

    }

    

    // 大转盘

    func didPlaylucky(){

        navigatorEx?.push(YPNavigationPath.Member.Playlucky.rawValue.navigatorPath)

    }

    

}

 

class YPBuoyViewManager: NSObject {

    

    /// 单例对象

    static let manager = YPBuoyViewManager()

    /// 视图池

    var suspendedList: [YPBuoyView] = []

    

    /// 存放的控制器类型

    private var buoyVcTypeList: [String: [Int]] = [:]

    

    /// 二维数组 : 1.控制器 2.id

    private var locationList: [String: [Int:CGRect]] = [:]

    

    override private init() {

        super.init()

    }

    

    /// 添加浮标

    func addBuoyView(vc: UIViewController,id: Int) -> Bool{

        let className = "\(vc.classForCoder)"

        if let tmpArray = buoyVcTypeList[className]{

            if tmpArray.firstIndex(of: id) != nil{

                return false

            }

        }

        return true

    }

    

    func addBuoyRect(vc: UIViewController,id: Int,rect: CGRect){

        let className = "\(vc.classForCoder)"

        var tmpArray = locationList[className] ?? [:]

        tmpArray[id] = rect

        locationList[className] = tmpArray

    }

    

    func getBuoyRect(vc: UIViewController,id: Int)->CGRect?{

        let className = "\(vc.classForCoder)"

        let tmpArray = locationList[className]

        return tmpArray?[id]

    }

    

    /// 删除浮标

    func delete(vc: UIViewController,id: Int) {

        let className = "\(vc.classForCoder)"

        var tmpArray = buoyVcTypeList[className] ?? []

        tmpArray.append(id)

        buoyVcTypeList[className] = tmpArray

        locationList[className]?.removeValue(forKey: id)

    }

    

}

 

/// MARK: - 拖拽的浮标

class YPBuoyView: View{

    

    ///  是不是能拖曳,默认为YES

    var dragEnable: Bool = true

    /// 起点

    var startPoint: CGPoint = .zero

    /// 拖拽的范围-指定父视图-拖出父视图后无法点击

    var freeRect: CGRect = .zero

    /// 是否自动黏贴边界,默认为true 不自动黏贴边界

    var isKeepBounds: Bool = true

    /// 记录model

    let model: YPBuoyListModel

    /// 图片大小

    let imgW: CGFloat

    let imgH: CGFloat

    

    /// 关闭按钮

    lazy var closeButton: YPSVGView = {

        let button = YPSVGView.init(name: "icon_xf_pop_close")

        button.scViewProperties("关闭按钮")

        return button

    }()

    

    /// 显示的图片

    lazy var iconView: UIImageView = {

        let iconView = UIImageView()

        iconView.contentMode = .scaleAspectFit

        //打开点击交互

        iconView.isUserInteractionEnabled = true

        iconView.scViewProperties("显示的图片")

        return iconView

    }()

    

    init(model: YPBuoyListModel) {

        self.model = model

        //根据关闭按钮的大小确定的偏移

        let offest: CGFloat = 18.scale

        let y: CGFloat = 300.scale

        /// 设置浮标位置大小

        if let size = model.size?.components(separatedBy: "*"),size.count==2{

            let w = size[0].axc_cgFloat

            let h = size[1].axc_cgFloat

            let x = YPScreen.screen_w - w - offest

            self.imgW = w

            self.imgH = h

            super.init(frame: CGRect(x, y, w + offest, h + offest))

        }else{

            self.imgW = 58

            self.imgH = 58

            let x = YPScreen.screen_w - self.imgW

            super.init(frame: CGRect(x, y, self.imgW, self.imgH))

        }

        /// 设置图片

        self.iconView.yp_load(model.iconUrl ?? "")

        /// 后台配置了才加手势

        if model.isDrag == 1{

            //添加拖拽手势

            let pan = UIPanGestureRecognizer(target: self, action: #selector(panAction(pan:)))

            pan.minimumNumberOfTouches = 1

            pan.maximumNumberOfTouches = 1

            self.addGestureRecognizer(pan)

        }

        /// 1 是需要关闭按钮

        if model.isClose == 1 {

            closeButton.isHidden = false

        }else{

            closeButton.isHidden = true

        }

    }

    

    required init?(coder: NSCoder) {

        fatalError("init(coder:) has not been implemented")

    }

    

    /// 初始化UI

    override func makeUI() {

        backgroundColor = .clear

        addSubview(iconView)

        iconView.snp.makeConstraints { (make) in

            make.bottom.left.equalToSuperview()

            make.width.equalTo(imgW)

            make.height.equalTo(imgH)

        }

        

        /// 关闭按钮布局

        addSubview(closeButton)

        closeButton.snp.makeConstraints { (make) in

            make.top.right.equalToSuperview()

            make.size.equalTo(24.scale)

        }

    }

}

/// MARK- 拖拽事件

extension YPBuoyView {

    @objc func panAction(pan: UIPanGestureRecognizer){

        /// 不能拖拽直接返回

        if dragEnable == false { return }

        switch pan.state {

        case .began:

            /// 开始拖拽

            pan.setTranslation(.zero, in: self)

            startPoint = pan.translation(in: self)

        case .changed: /// 拖拽改变

            let point = pan.translation(in: self)

            var dx: CGFloat = 0

            var dy: CGFloat = 0

            dx = point.x - startPoint.x

            dy = point.y - startPoint.y

            let newCenter = CGPoint(center.x + dx, center.y + dy)

            center = newCenter

            pan.setTranslation(.zero, in: self)

        case .ended:

            /// 拖拽结束

            keepBounds()

        default:

            break

        }

    }

    override func willMove(toSuperview newSuperview: UIView?) {

        if (newSuperview != nil){

            if freeRect == .zero{

                freeRect = newSuperview?.bounds ?? .zero

            }

            keepBounds()

        }

    }

    /// 停止拖拽后保持范围之内

    func keepBounds() {

        let centerX = CGFloat(freeRect.origin.x+(freeRect.size.width - frame.size.width)/2)

        var rect = frame

        /// 不吸边

        if isKeepBounds == true {

            if frame.origin.x < freeRect.origin.x {

                UIView.animate(withDuration: 0.25) {[weak self] in

                    guard let weakSelf = self else {return}

                    rect.origin.x = weakSelf.freeRect.origin.x

                    weakSelf.frame = rect

                }

            }else if freeRect.origin.x+freeRect.size.width < frame.origin.x+frame.size.width {

                UIView.animate(withDuration: 0.25) {[weak self] in

                    guard let weakSelf = self else {return}

                    rect.origin.x = weakSelf.freeRect.origin.x+weakSelf.freeRect.size.width-weakSelf.frame.size.width

                    weakSelf.frame = rect

                }

            }

        }else{

            if frame.origin.x < centerX {/// 小于中心点

                UIView.animate(withDuration: 0.25) {[weak self] in

                    guard let weakSelf = self else {return}

                    rect.origin.x = weakSelf.freeRect.origin.x

                    weakSelf.frame = rect

                }

            }else{

                UIView.animate(withDuration: 0.25) {[weak self] in

                    guard let weakSelf = self else {return}

                    rect.origin.x = weakSelf.freeRect.origin.x+weakSelf.freeRect.size.width - weakSelf.frame.size.width

                    weakSelf.frame = rect

                }

            }

        }

        let freeRectY = freeRect.origin.y+StatusHeight

        let originFrame = frame.origin.y+frame.size.height

        let freeRectH = freeRect.origin.y+freeRect.size.height

        let originTotelFrame = originFrame+TabBarHeight

        if frame.origin.y < freeRectY+130 { /// 修改范围

            UIView.animate(withDuration: 0.25) {[weak self] in

                guard let weakSelf = self else {return}

                rect.origin.y = weakSelf.freeRect.origin.y+StatusHeight+130

                weakSelf.frame = rect

            }

        }else if freeRectH < originTotelFrame{ /// 拖动到底部

            UIView.animate(withDuration: 0.25) {[weak self] in

                guard let weakSelf = self else {return}

                rect.origin.y = weakSelf.freeRect.origin.y+weakSelf.freeRect.size.height-weakSelf.frame.size.height-TabBarHeight

                weakSelf.frame = rect

            }

        }

        /// 滚动了以后更新单例里面对应id的frame

        DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {

            if let currentVC = UIViewController.getCurrent {

                YPBuoyViewManager.manager.addBuoyRect(vc: currentVC,

                                                      id: self.model.id ?? 0, rect: self.frame)

            }

        }

    }

}

 

posted @   super1250  阅读(174)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
历史上的今天:
2016-04-07 swift集成alamofire的简单封装
点击右上角即可分享
微信分享提示