版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lin1109221208/article/details/80985435
需求:实现一个可缩放界面的悬浮窗,当界面缩小时,可以跟随手势滑动,并且可以继续操作其他界面
思路:将悬浮窗看成一个视图,视图上有一个缩放按钮,再将需要缩放的视图控制器的view加入悬浮窗视图,缩放按钮在界面放大时,可以自定义按钮的大小及位置,缩放按钮在界面缩小时,按钮大小与悬浮窗大小一致
实现效果如下
具体实现:
1、自定义悬浮窗视图
(1)重写UIView的init(frame:CGRect)方法,并添加缩放按钮及缩放界面,添加视图滑动的手势,计算视图初始展示的中心点及位置,将视图加入window,并展示在最上层
enum SuspendedBallLocation:Int {
case SuspendedBallLocation_LeftTop = 0
case SuspendedBallLocation_Top
case SuspendedBallLocation_RightTop
case SuspendedBallLocation_Right
case SuspendedBallLocation_RightBottom
case SuspendedBallLocation_Bottom
case SuspendedBallLocation_LeftBottom
case SuspendedBallLocation_Left
}
private var ballBtn:UIButton?
private var timeLable:UILabel?
private var currentCenter:CGPoint?
private var panEndCenter:CGPoint = CGPoint.init(x: 0, y: 0)
private var currentLocation:SuspendedBallLocation?
var callingVC : ViewController!
override init(frame: CGRect) {
super.init(frame: CGRect.init(x: 0, y: 0, width: frame.size.width, height: frame.size.height))
//增加呼出界面
let storyboard = UIStoryboard(name: "Main", bundle: nil)
callingVC = (storyboard.instantiateViewController(withIdentifier: "VC")as?ViewController)!
self.addSubview(callingVC.view)
ballBtn = UIButton.init(type: .custom)
ballBtn?.setBackgroundImage(UIImage.init(named: "shrink"), for: .normal)
ballBtn?.imageView?.contentMode = .center
ballBtn?.frame = CGRect.init(x: ScreenWidth-51, y: 40, width: 31, height: 23)
ballBtn?.addTarget(self, action: #selector(clickBallViewAction), for: .touchUpInside)
self.addSubview(self.ballBtn!)
self.backgroundColor = UIColor.clear
self.currentCenter = CGPoint.init(x: frame.size.width/2, y: frame.size.height/2) //初始位置
self.calculateShowCenter(point: self.currentCenter!)
self.configLocation(point: self.currentCenter!)
//跟随手指拖动
let moveGes:UIPanGestureRecognizer = UIPanGestureRecognizer.init(target: self, action: #selector(self.dragBallView))
self.addGestureRecognizer(moveGes)
//添加到window上
self.ww_getKeyWindow().addSubview(self)
//显示在视图的最上层
self.ww_getKeyWindow().bringSubview(toFront: self)
}
//MARK:- private utility
func ww_getKeyWindow() -> UIWindow {
if UIApplication.shared.keyWindow == nil {
return ((UIApplication.shared.delegate?.window)!)!
}else{
return UIApplication.shared.keyWindow!
}
}
//计算浮窗展示的中心点
func calculateShowCenter(point:CGPoint) {
unowned let weakSelf = self
UIView.animate(withDuration: 0.3) {
weakSelf.center = CGPoint.init(x: point.x, y: point.y)
}
}
//当前方位
func configLocation(point:CGPoint) {
if (point.x <= centerX*3 && point.y <= centerY*3) {
self.currentLocation = .SuspendedBallLocation_LeftTop;
}
else if (point.x>centerX*3 && point.x<ScreenWidth-centerX*3 && point.y == centerY)
{
self.currentLocation = .SuspendedBallLocation_Top;
}
else if (point.x >= ScreenWidth-centerX*3 && point.y <= 3*centerY)
{
self.currentLocation = .SuspendedBallLocation_RightTop;
}
else if (point.x == ScreenWidth-centerX && point.y>3*centerY && point.y<ScreenHeight-centerY*3)
{
self.currentLocation = .SuspendedBallLocation_Right;
}
else if (point.x >= ScreenWidth-3*centerX && point.y >= ScreenHeight-3*centerY)
{
self.currentLocation = .SuspendedBallLocation_RightBottom;
}
else if (point.y == ScreenHeight-centerY && point.x > 3*centerX && point.x<ScreenWidth-3*centerX)
{
self.currentLocation = .SuspendedBallLocation_Bottom;
}
else if (point.x <= 3*centerX && point.y >= ScreenHeight-3*centerY)
{
self.currentLocation = .SuspendedBallLocation_LeftBottom;
}
else if (point.x == centerX && point.y > 3*centerY && point.y<ScreenHeight-3*centerY)
{
self.currentLocation = .SuspendedBallLocation_Left;
}
}
(2)缩放按钮点击方法:根据判断悬浮窗视图的width是否等于整个屏幕的width来决定视图执行的操作,若等于,则视图执行缩小操作,反之则放大
//MARK:- 悬浮窗按钮方法
@objc func clickBallViewAction() {
if self.frame.size.width == ScreenWidth {
//缩小
//固定缩放的中心点为右上角
var frame = self.callingVC.view.frame
self.layer.anchorPoint = CGPoint.init(x: 1, y: 0)
self.frame = frame
self.smallToShow()
var callingVCFrame = self.callingVC.view.frame
callingVCFrame.origin.y -= 70
callingVCFrame.origin.x -= 50
callingVCFrame.size.width += 100
callingVCFrame.size.height += 140
self.ballBtn?.frame = callingVCFrame
ballBtn?.setBackgroundImage(UIImage.init(named: "拨打中"), for: .normal)
ballBtn?.imageView?.contentMode = .scaleToFill
let point = CGPoint.init(x: ScreenWidth-centerX, y:centerY) //初始位置
self.calculateShowCenter(point: point)
self.configLocation(point: point)
}else{
//放大
//固定缩放的中心点为右上角
let frame = CGRect.init(x: 0, y: 0, width: ScreenWidth, height: ScreenHeight)
self.layer.anchorPoint = CGPoint.init(x: 1, y: 0)
self.frame = frame
self.bigToShow()
self.frame = CGRect.init(x: 0, y: 0, width: ScreenWidth, height: ScreenHeight)
self.ballBtn?.frame = CGRect.init(x: ScreenWidth-51, y: 40, width: 31, height: 23)
ballBtn?.setBackgroundImage(UIImage.init(named: "shrink"), for: .normal)
ballBtn?.imageView?.contentMode = .center
}
}
//放大
func bigToShow(){
let animation = CAKeyframeAnimation.init(keyPath: "transform")
animation.duration = 0.5
let values = NSMutableArray.init()
values.add(NSValue.init(caTransform3D: CATransform3DMakeScale(0.2, 0.17, 1.0)))
values.add(NSValue.init(caTransform3D: CATransform3DMakeScale(1.0, 1.0, 1.0)))
animation.values = values as? [Any]
self.layer.add(animation, forKey: nil)
self.layer.transform = CATransform3DMakeScale(1, 1, 1)
}
//缩小
func smallToShow(){
let animation = CAKeyframeAnimation.init(keyPath: "transform")
animation.duration = 0.5
let values = NSMutableArray.init()
values.add(NSValue.init(caTransform3D: CATransform3DMakeScale(1.0, 1.0, 1.0)))
values.add(NSValue.init(caTransform3D: CATransform3DMakeScale(0.2, 0.17, 1.0)))
animation.values = values as? [Any]
self.layer.add(animation, forKey: nil)
self.layer.transform = CATransform3DMakeScale(0.2, 0.17, 1)
}
(3)滑动手势方法:根据手势最后停留的中心点计算悬浮窗停留的中心位置,中心点区域主要氛围9个部分,如图所示:
具体代码如下:
//跟随手指拖动
@objc func dragBallView(panGes:UIPanGestureRecognizer) {
if self.frame.size.width != ScreenWidth {
let translation:CGPoint = panGes.translation(in: self.ww_getKeyWindow())
let center:CGPoint = self.center
self.center = CGPoint.init(x: center.x+translation.x, y: center.y+translation.y)
panGes .setTranslation(CGPoint.init(x: 0, y: 0), in: self.ww_getKeyWindow())
if panGes.state == UIGestureRecognizerState.ended{
self.panEndCenter = self.center
self.caculateBallCenter()
}
}
}
//计算中心位置
func caculateBallCenter() {
if (self.panEndCenter.x>centerX && self.panEndCenter.x < ScreenWidth-centerX && self.panEndCenter.y>centerY && self.panEndCenter.y<ScreenHeight-centerY) {
//在上下左右距离边30的矩形里
if (self.panEndCenter.y<3*centerY) {
if self.panEndCenter.x < 4*centerX {
self.calculateBallNewCenter(point: CGPoint.init(x: 3*centerX , y: 30+centerY))
}else{
self.calculateBallNewCenter(point: CGPoint.init(x: self.panEndCenter.x, y: 30+centerY))
}
}
else if (self.panEndCenter.y>ScreenHeight-3*centerY)
{
// 左下角
//
if self.panEndCenter.x < 4*centerX {
self.calculateBallNewCenter(point: CGPoint.init(x: centerX+2*centerY, y: ScreenHeight-4*centerY-10))
}else{
self.calculateBallNewCenter(point: CGPoint.init(x: self.panEndCenter.x, y: ScreenHeight-4*centerY-10))
}
}
else
{
if (self.panEndCenter.x<=ScreenWidth/2) {
self.calculateBallNewCenter(point: CGPoint.init(x: centerX+2*centerY, y: self.panEndCenter.y))
}
else{
self.calculateBallNewCenter(point: CGPoint.init(x: ScreenWidth-centerX, y: self.panEndCenter.y))
}
}
}
else
{
if (self.panEndCenter.x<=centerX && self.panEndCenter.y<=centerY)
{
self.calculateBallNewCenter(point: CGPoint.init(x: 3*centerX, y: centerY))
}
else if (self.panEndCenter.x>=ScreenWidth-centerX && self.panEndCenter.y<=centerY)
{
//右上角
self.calculateBallNewCenter(point: CGPoint.init(x: ScreenWidth-centerX, y:centerY))
}
else if (self.panEndCenter.x>=ScreenWidth-centerX && self.panEndCenter.y>=ScreenHeight-centerY)
{
self.calculateBallNewCenter(point: CGPoint.init(x: ScreenWidth-centerX, y: ScreenHeight-centerY))
}
else if(self.panEndCenter.x<=centerX && self.panEndCenter.y>=ScreenHeight-centerY)
{
self.calculateBallNewCenter(point: CGPoint.init(x: centerX, y: ScreenHeight-centerY))
}
else if (self.panEndCenter.x>centerX && self.panEndCenter.x<ScreenWidth-centerX && self.panEndCenter.y<centerY)
{
//左上角
if self.panEndCenter.x<4*centerX{
self.calculateBallNewCenter(point: CGPoint.init(x: 3*centerX, y: centerY))
}else{
self.calculateBallNewCenter(point: CGPoint.init(x: self.panEndCenter.x, y: centerY))
}
}
else if (self.panEndCenter.x>centerX && self.panEndCenter.x<ScreenWidth-centerX && self.panEndCenter.y>ScreenHeight-centerY)
{
self.calculateBallNewCenter(point: CGPoint.init(x: self.panEndCenter.x, y:ScreenHeight-centerY))
}
else if (self.panEndCenter.y>centerY && self.panEndCenter.y<ScreenHeight-centerY && self.panEndCenter.x<centerX)
{
self.calculateBallNewCenter(point: CGPoint.init(x: 3*centerX, y: self.panEndCenter.y))
}
else if (self.panEndCenter.y>centerY && self.panEndCenter.y<ScreenHeight-centerY && self.panEndCenter.x>ScreenWidth-centerX)
{
//右下角
if self.panEndCenter.y>ScreenHeight-4*centerY {
self.calculateBallNewCenter(point: CGPoint.init(x: ScreenWidth-centerX, y:ScreenHeight-4*centerY-10))
}else{
self.calculateBallNewCenter(point: CGPoint.init(x: ScreenWidth-centerX, y:self.panEndCenter.y))
}
}
}
}
//计算浮窗新的中心点
func calculateBallNewCenter(point:CGPoint) {
self.currentCenter = point
self.configLocation(point: point)
unowned let weakSelf = self
UIView.animate(withDuration: 0.3) {
weakSelf.center = CGPoint.init(x: point.x, y: point.y)
}
}
2、点击界面“点击弹出悬浮窗”按钮,弹出界面
整体框架如图所示
具体代码如下:
import UIKit
var ballView : IMOSuspendedBallView!
class FViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
UserDefaults.standard.setValue("0", forKey: "ballViewSave")
// Do any additional setup after loading the view.
}
@IBAction func click(_ sender: Any) {
if UserDefaults.standard.object(forKey: "ballViewSave")as! String == "1" {
let aleat = UIAlertController(title: "提示", message:"当前已有悬浮窗", preferredStyle: UIAlertControllerStyle.alert)
let tempAction = UIAlertAction(title: "知道了", style: .cancel) { (action) in
}
aleat.addAction(tempAction)
self.present(aleat, animated: true, completion: {
})
}else{
ballView = IMOSuspendedBallView.init(frame: CGRect.init(x: 0, y: 0, width: ScreenWidth, height: ScreenHeight))
UserDefaults.standard.setValue("1", forKey: "ballViewSave")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
3、弹出界面代码如下:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.lightGray
}
@IBAction func buttonClick(_ sender: Any) {
ballView.dismissView()
}
}
综上所述,具体代码实现:swift 实现可缩放悬浮窗