[Swift]在window上添加可拖动悬浮视图

文章介绍了如何在iOS中创建一个可固定、可拖动并有限制范围的悬浮视图,常用于悬浮广告。提供了GASuspendView类的代码实现,包括视图配置、拖动手势处理和限制条件。同时,GASuspendManager类用于管理悬浮视图的显示和隐藏,以及根据页面切换更新状态。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一个添加在window上悬浮视图。可固定,可拖动,还可以限制一个范围内拖动,可限定在只某些页面展示。

主要是用来解决项目中弹出的悬浮广告,类似悬浮球。

Demo:

https://github.com/Gamin-fzym/GASuspendViewDemo

https://download.csdn.net/download/u012881779/87614648

实现:

悬浮视图和视图配置

import Foundation
import UIKit
import Kingfisher

class GASuspendView: UIView {
    
    lazy var thumbIV : UIImageView = {
        let iv = UIImageView()
        iv.backgroundColor = .clear
        iv.contentMode = .scaleAspectFill
        iv.isUserInteractionEnabled = false
        return iv
    }()
    var vProperty: GASuspendViewProperty? {
        didSet {
            guard let vo = vProperty else { return }
            self.frame = CGRectMake(vo.startPoint.x, vo.startPoint.y, vo.width, vo.height)
            self.layer.cornerRadius = vo.corner
            self.layer.masksToBounds = true
            thumbIV.frame = self.bounds
        }
    }
    var clickedBlock: ((GASuspendModel?) -> ())?
    var imageLoadFinish: (() -> ())?
    var model: GASuspendModel? {
        didSet {
            guard let vo = model else { return }
            thumbIV.kf.setImage(with: URL(string: vo.thumbPath)) { [weak self] result in
                DispatchQueue.main.async {
                    switch result {
                    case .success(let value):
                        let _ = value.image
                        self?.imageLoadFinish?()
                        break
                    case .failure(_):
                        break
                    }
                }
            }
        }
    }
    
    deinit {
        clickedBlock = nil
        imageLoadFinish = nil
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.addSubview(thumbIV)
        let tap = UITapGestureRecognizer(target: self, action: #selector(tapAction(gesture:)))
        self.addGestureRecognizer(tap)
        let pan = UIPanGestureRecognizer(target: self, action: #selector(panAction(gesture:)))
        self.addGestureRecognizer(pan)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError()
    }
    
    @objc func tapAction(gesture: UITapGestureRecognizer) {
        clickedBlock?(model)
    }
    
    @objc func panAction(gesture: UIPanGestureRecognizer) {
        guard let window = GASuspendManager.appCurrentWindow() else { return }
        window.bringSubviewToFront(self)
        let translation = gesture.translation(in: window)
        var center = CGPoint(x: self.center.x + translation.x, y: self.center.y + translation.y)
        if let limitBounds = vProperty?.limitBounds, let width = vProperty?.width, let height = vProperty?.height {
            let limitCenterRect = CGRect(x: limitBounds.origin.x + width/2.0,
                                         y: limitBounds.origin.y + height/2.0,
                                         width: limitBounds.size.width - width,
                                         height: limitBounds.size.height - height)
            if !limitCenterRect.contains(center) {
                if center.x < limitCenterRect.origin.x {
                    center.x = limitCenterRect.origin.x
                }
                if center.x > limitCenterRect.origin.x + limitCenterRect.size.width {
                    center.x = limitCenterRect.origin.x + limitCenterRect.size.width
                }
                if center.y < limitCenterRect.origin.y {
                    center.y = limitCenterRect.origin.y
                }
                if center.y > limitCenterRect.origin.y + limitCenterRect.size.height {
                    center.y = limitCenterRect.origin.y + limitCenterRect.size.height
                }
            }
        }
        self.center = center
        gesture.setTranslation(CGPoint.zero, in: window)
    }
    
}

// MARK: - 浮窗的相关配置属性
class GASuspendViewProperty: NSObject {
    
    /// 限制滑动区域
    var limitBounds: CGRect = UIScreen.main.bounds
    /// 浮窗宽度
    var width: CGFloat = 81
    /// 浮窗高度
    var height: CGFloat = 168
    /// 开始位置
    var startPoint: CGPoint = CGPoint(x: 0, y: 0)
    /// 圆角
    var corner: CGFloat = 0
    /// 边距
    var padding: CGFloat = 10

}

悬浮视图管理

import Foundation
import UIKit

class GASuspendManager {
    
    static let shared = GASuspendManager()
    private var suspendModel: GASuspendModel?

    lazy var suspendView: GASuspendView = {
        let view = GASuspendView(frame: .zero)
        view.tag = 88888
        view.imageLoadFinish = {
            view.isHidden = false
        }
        view.clickedBlock = { [weak self] model in
            print("tap suspendView")
        }
        view.isHidden = true
        return view
    }()
    
    func setSuspendData() {
        setSuspendData(completed: {_ in })
    }
    
    func setSuspendData(completed: @escaping ((GASuspendModel?) -> ())) {
        // request data
        let model = GASuspendModel()
        setSuspendData(model)
        completed(model)
        GASuspendManager.updateSuspendViewShow()
    }
    
    func setSuspendData(_ model: GASuspendModel) {
        suspendModel = model
        setupSuspendView(model)
    }
    
    func setupSuspendView(_ model: GASuspendModel) {
        let vProperty = GASuspendViewProperty()
        vProperty.padding = model.padding
        vProperty.width = model.width
        vProperty.height = model.height
        vProperty.corner = model.corner
        
        // 限制在一个范围内拖动
        vProperty.limitBounds = CGRectMake(vProperty.padding,
                                           navigationBarHeight() + vProperty.padding,
                                           screenWidth()-vProperty.padding*2,
                                           screenHeight() - navigationBarHeight() - tabbarHeight() - 2*vProperty.padding)
        /*
        // 限制只能挨着右侧边框上下拖动
        vProperty.limitBounds = CGRectMake(screenWidth() - vProperty.padding - vProperty.width,
                                           navigationBarHeight() + vProperty.padding,
                                           vProperty.width,
                                           screenHeight() - navigationBarHeight() - tabbarHeight() - 2*vProperty.padding)
         */
        /*
        // 限制只能挨着底部边框左右拖动
        vProperty.limitBounds = CGRectMake(vProperty.padding,
                                           screenHeight() - vProperty.padding - vProperty.height - tabbarHeight(),
                                           screenWidth() - 2*vProperty.padding,
                                           vProperty.height)
         */
                 
        vProperty.startPoint = CGPoint(x: vProperty.limitBounds.origin.x + vProperty.limitBounds.size.width - vProperty.width ,
                                       y: vProperty.limitBounds.origin.y + vProperty.limitBounds.size.height - vProperty.height)
        vProperty.corner = model.corner
        suspendView.vProperty = vProperty
        suspendView.isHidden = true
        suspendView.model = model
        GASuspendManager.appCurrentWindow()?.addSubview(suspendView)
    }
    
    /// 从window获取SuspendView
    func getSuspendViewInWindow() -> GASuspendView? {
        if let view = GASuspendManager.appCurrentWindow()?.viewWithTag(88888) as? GASuspendView {
            return view
        }
        return nil
    }
    
    /// 更新悬浮视图状态
    static func updateSuspendViewShow() {
        let limitVCArr: [String] = ["ViewController", "HomeVC"]
        if let topVC = GASuspendManager.topViewController(), limitVCArr.contains(topVC.className) {
            if GASuspendManager.shared.suspendModel == nil {
                GASuspendManager.shared.getSuspendViewInWindow()?.isHidden = true
            } else {
                GASuspendManager.shared.getSuspendViewInWindow()?.isHidden = false
                if let view = GASuspendManager.shared.getSuspendViewInWindow() {
                    GASuspendManager.appCurrentWindow()?.bringSubviewToFront(view)
                }
            }
        } else {
            GASuspendManager.shared.getSuspendViewInWindow()?.isHidden = true
        }
    }
    
}

示意图:

1.限制悬浮视图只能在一个范围内拖动

2.限制悬浮视图只能靠右上下拖动 

 3.限制悬浮视图只能靠底部左右拖动

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值