• 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)

                }

            }

        }

    }

  • 相关阅读:
    Flutter | 如何优雅的解决依赖版本冲突
    Flutter包依赖冲突解决方法
    Cannot run with sound null safety because dependencies don't support null safety
    软件工程实践2019第三次作业
    【题解】洛谷 P3704 [SDOI2017]数字表格
    关于学历和素质成正比的隐晦思考
    斐波那契数列通项公式
    某不知名比赛游记
    CCPC-final 2020 北京游记
    EC-final 2020-2021 西安游记
  • 原文地址:https://www.cnblogs.com/supersr/p/16113414.html
Copyright © 2020-2023  润新知