Swift 仿微信语音通话最小化时后的效果

1,502 阅读2分钟

最近碰到个需求,需要仿微信语音通话缩小化后,保持界面最上层有一个悬浮的小View可以一点击就把刚刚缩放掉的界面再放回来,其实本质就是创造了一个新的Window,在这个window上创建了一个rootController并展示他,缩小化时是把controller dismiss掉了,再次点击那个小View之后把这个controller再展示出来便可以了。同理微信小程序其实也是在一个新的Window中做了一套新的逻辑。随着现在手机性能的提升,多Window同时存在并不会造成严重卡顿,而衍生出来的一种新的开发方式。

上代码,这个是根据网上找到的类似效果进行了部分修改的,作者叫冯琦帆

SuspendTool

import Foundation
import UIKit

enum SuspendType {
  case none
  case single
  case multi
}

class SuspendTool: NSObject {

  static let sharedInstance = SuspendTool()
  private var suspendWindows: [SuspendWindow] = []
//  var semicircle: Semicircle?
  var origin: CGPoint = CGPoint.init(x: 10, y: 300)

  static func showSuspendWindow(rootViewController: UIViewController, coverImageName: String) {
    let tool = SuspendTool.sharedInstance
    let window = SuspendWindow.init(rootViewController: rootViewController, coverImageName: coverImageName, frame: CGRect.init(origin: tool.origin, size: CGSize.init(width: radious, height: radious)))
    window.show()
    tool.suspendWindows.append(window)
  }

  static func replaceSuspendWindow(rootViewController: UIViewController, coverImageName: String) {
    let tool = SuspendTool.sharedInstance
    tool.suspendWindows.removeAll()
    let window = SuspendWindow.init(rootViewController: rootViewController, coverImageName: coverImageName, frame: CGRect.init(origin: tool.origin, size: CGSize.init(width: radious, height: radious)))
    window.show()
    tool.suspendWindows.append(window)
  }

  static func remove(suspendWindow: SuspendWindow) {
    UIView.animate(withDuration: 0.25, animations: {
      suspendWindow.alpha = 0
    }) { (complete) in
      if let index = SuspendTool.sharedInstance.suspendWindows.index(of: suspendWindow) {
        SuspendTool.sharedInstance.suspendWindows.remove(at: index)
      }
    }
  }

  static func setLatestOrigin(origin: CGPoint) {
    SuspendTool.sharedInstance.origin = origin
  }
}

SuspendWindow

import UIKit

let radious: CGFloat = 82

class SuspendWindow: UIWindow {
    
    fileprivate let coverImageName: String
    fileprivate let space: CGFloat = 15
    var containsRootViewController: UIViewController?
    
    init(rootViewController: UIViewController ,coverImageName: String, frame: CGRect) {
        self.coverImageName = coverImageName
        super.init(frame: frame)
        //    self.rootViewController = rootViewController
        self.containsRootViewController = rootViewController
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func show() {
        self.backgroundColor = UIColor.clear
        self.windowLevel = UIWindow.Level.alert - 1//UIWindowLevelAlert - 1
        self.screen = UIScreen.main
        self.isHidden = false
        
        let bgView = UIView()
        bgView.isUserInteractionEnabled = true
        bgView.frame = self.bounds
        bgView.backgroundColor = UIColor.white
        bgView.layer.cornerRadius = radious / 2.0
        bgView.layer.borderColor = UIColor.lightGray.cgColor
        bgView.layer.borderWidth = 5
        bgView.layer.masksToBounds = true
        self.addSubview(bgView)
        bgView.addSubview(iconImageView)
        bgView.addSubview(timeLabel)
        
        let panGesture = UIPanGestureRecognizer.init(target: self, action: #selector(didPan(_:)))
        self.addGestureRecognizer(panGesture)
        
        let tapGesture = UITapGestureRecognizer.init(target: self, action: #selector(didTap(_:)))
        self.addGestureRecognizer(tapGesture)
    }
    
    @objc fileprivate func didTap(_ tapGesture: UITapGestureRecognizer) {
        SuspendTool.sharedInstance.origin = self.frame.origin
        self.containsRootViewController?.spread(from: self.self.frame.origin)
        SuspendTool.remove(suspendWindow: self)
    }
    
    @objc fileprivate func didPan(_ panGesture: UIPanGestureRecognizer) {
        let point = panGesture.translation(in: panGesture.view)
        var originX = self.frame.origin.x + point.x
        if originX < space {
            originX = space
        } else if originX > UIScreen.main.bounds.width - radious - space {
            originX = UIScreen.main.bounds.width - radious - space
        }
        var originY = self.frame.origin.y + point.y
        if originY < space {
            originY = space
        } else if originY > UIScreen.main.bounds.height - radious - space {
            originY = UIScreen.main.bounds.height - radious - space
        }
        self.frame = CGRect.init(x: originX, y: originY, width: self.bounds.width, height: self.bounds.height)
        if panGesture.state == UIGestureRecognizer.State.cancelled || panGesture.state == UIGestureRecognizer.State.ended || panGesture.state == UIGestureRecognizer.State.failed {
            self.adjustFrameAfterPan()
        }
        panGesture.setTranslation(CGPoint.zero, in: self)
    }
    
    fileprivate func adjustFrameAfterPan() {
        var originX: CGFloat = space
        if self.center.x < UIScreen.main.bounds.width / 2 {
            originX = space
        } else if self.center.x >= UIScreen.main.bounds.width / 2 {
            originX = UIScreen.main.bounds.width - radious - space
        }
        UIView.animate(withDuration: 0.25, animations: {
            self.frame = CGRect.init(x: originX, y: self.frame.origin.y, width: self.frame.size.width, height: self.frame.size.height)
        }) { (complete) in
            SuspendTool.setLatestOrigin(origin: self.frame.origin)
        }
    }
    lazy var timeLabel: UILabel = {
        let timeLabel = UILabel()
        timeLabel.frame = CGRect(x: 0, y: 55.5, width: 42, height: 13)
        timeLabel.center.x = self.bounds.size.width / 2
        timeLabel.textAlignment = .center
        timeLabel.text = "0:00"
        timeLabel.textColor = UIColor.text
        timeLabel.font = UIFont.mediumFont(ofSize: 13)
        return timeLabel
    }()
    
    lazy var iconImageView: UIImageView = {
        let iconImageView = UIImageView.init(image: UIImage.init(named: coverImageName))
        iconImageView.isUserInteractionEnabled = true
        iconImageView.frame = CGRect(x: 0, y: 12, width: 38, height: 38)
        iconImageView.center.x = self.bounds.size.width / 2
        return iconImageView
    }()
}

UIViewController+FF

import Foundation
import UIKit

extension UIViewController {

  func suspend(coverImageName: String, type: SuspendType) {
    if type == .none {
      self.navigationController?.popViewController(animated: true)
      return
    }
    self.view.layer.masksToBounds = true
    UIView.animate(withDuration: 0.25, animations: {
      self.view.layer.cornerRadius = radious / 2.0
      self.view.frame = CGRect.init(origin: SuspendTool.sharedInstance.origin, size: CGSize.init(width: radious, height: radious))
      self.view.layoutIfNeeded()
    }) { (complete) in
      self.navigationController?.popViewController(animated: false)
      if type == .single {
        SuspendTool.replaceSuspendWindow(rootViewController: self, coverImageName: coverImageName)
      } else {
        SuspendTool.showSuspendWindow(rootViewController: self, coverImageName: coverImageName)
      }
    }
  }

  func spread(from point: CGPoint) {
    if let isContain = self.navigationController?.viewControllers.contains(self), isContain {
      return
    }
    self.view.frame = CGRect.init(origin: point, size: CGSize.init(width: radious, height: radious))
    //UIViewController.currentViewController()
    
    UIViewController.currentViewController().navigationController?.pushViewController(self, animated: false)
    UIView.animate(withDuration: 0.25, animations: {
      self.view.layer.cornerRadius = 0
      self.view.frame = UIScreen.main.bounds
      self.view.layoutIfNeeded()
    })
  }

  static func currentViewController() -> UIViewController {
    var rootViewController: UIViewController? = nil
    for window in UIApplication.shared.windows {
      if (window.rootViewController != nil) {
        rootViewController = window.rootViewController
        break
      }
    }
    var viewController = rootViewController
    while (true) {
      if viewController?.presentedViewController != nil {
        viewController = viewController!.presentedViewController
      } else if viewController!.isKind(of: UINavigationController.self) {
        viewController = (viewController as! UINavigationController).visibleViewController
      } else if viewController!.isKind(of: UITabBarController.self) {
        viewController = (viewController as! UITabBarController).selectedViewController
      } else {
        break
      }
    }
    return viewController!
  }

}