思路
为了方便测试,开发者们一般都会写个日志上报的组件,通过上报的内容,很方便检查接口的情况。首先我们需要定义一个只有测试环境和预发布环境才能显示的日志浮窗,通过点击浮窗显示一个日志上报的列表弹窗。UITableView
的组头我们添加一个手势,实现cell
的折叠效果,当cell展开时,显示上报的日志内容。cell
的大小根据内容来决定,并实现日志请求发至企业微信、复制、清除、关闭日志弹窗等功能。
- 一、浮窗按钮
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let pt = touches.first?.location(in: self)
startLocation = pt
superview?.bringSubviewToFront(self)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let pt = touches.first?.location(in: self)
guard let pt = pt,
let startLocation = startLocation,
let superview = superview else {
return
}
let dx = pt.x - startLocation.x
let dy = pt.y - startLocation.y
var newCenter = CGPoint(x: center.x + dx, y: center.y + dy)
let halfx = self.bounds.width / 2
newCenter.x = max(halfx, newCenter.x)
newCenter.x = min(superview.bounds.size.width - halfx, newCenter.x)
let halfy = self.bounds.height / 2
newCenter.y = max(halfy, newCenter.y)
newCenter.y = min(superview.bounds.size.height - halfy, newCenter.y)
center = newCenter
}
private func locationChanged() {
guard let superview = superview else {
return
}
let point = center
if point.x > superview.frame.size.width / 2 {
UIView.animate(withDuration: 0.2) { [weak self] in
guard let self = self else {return}
self.frame = CGRect(x: superview.frame.size.width - self.frame.size.width,
y: self.frame.origin.y,
width: self.frame.size.width,
height: self.frame.size.height)
}
} else {
UIView.animate(withDuration: 0.2) { [weak self] in
guard let self = self else {return}
self.frame = CGRect(x: 0,
y: self.frame.origin.y,
width: self.frame.size.width,
height: self.frame.size.height)
}
}
}
二、日志浮窗的显示
class FSLogView {
static let shared = FSLogView()
// 动画时长
private var duration: TimeInterval = 0.25;
// 弹窗内容
var contentView: UIView?
// 是否允许点击阴影消失
var dismiss: Bool = true
lazy var popView: FSLogPopView = {
let window = getWindow()
let view = FSLogPopView(frame: CGRect(x: 0, y: 0, width: window!.frame.size.width, height: window!.frame.size.height))
view.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5)
view.addTarget(self, action: #selector(popViewAction), for: .touchUpInside)
return view
}()
private init() {}
// 显示弹窗
func show(view: UIView) {
self.show(view: view, dismiss: false)
}
func show(view: UIView, dismiss: Bool) {
view.center = CGPoint(x: popView.frame.size.width/2, y: popView.frame.size.height/2)
self.showAlertView(view: view, origin: view.frame.origin, duration: 0.25, dismiss: dismiss)
}
// 隐藏弹窗
func hidden() {
dismissAnimate()
}
// MARK: Events
@objc func popViewAction(sender: UIControl) {
if dismiss {
self.dismissAnimate()
}
}
}
extension FSLogView {
// 显示弹窗
fileprivate func showAlertView(view: UIView, origin: CGPoint, duration: TimeInterval, dismiss: Bool) {
removeSubViews()
let window = UIApplication.shared.delegate?.window
view.frame = CGRect(origin: origin, size: view.frame.size)
popView.addSubview(view)
window??.addSubview(self.popView)
contentView = view
self.duration = duration
self.dismiss = dismiss
showAnimate()
}
//显示动画
fileprivate func showAnimate() {
self.popView.alpha = 0
self.popView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5)
UIView.animate(withDuration: self.duration) {
self.popView.alpha = 1
}
}
// 隐藏动画
fileprivate func dismissAnimate() {
UIView.animate(withDuration: 0.25, animations: {
self.popView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0)
self.popView.alpha = 0
}) { (_) in
self.removeSubViews()
}
}
// 移除弹窗
fileprivate func removeSubViews() {
for view in self.popView.subviews {
view.removeFromSuperview()
}
self.popView.removeFromSuperview()
self.contentView = nil
}
}
extension FSLogView {
// 获取顶层控制器 根据window
fileprivate func getTopVC() -> (UIViewController?) {
let window = self.getWindow()
let vc = window?.rootViewController
return getTopVC(withCurrentVC: vc)
}
///根据控制器获取 顶层控制器
fileprivate func getTopVC(withCurrentVC VC :UIViewController?) -> UIViewController? {
if VC == nil {
print("【日志上报】找不到顶层控制器")
return nil
}
if let presentVC = VC?.presentedViewController {
//modal出来的 控制器
return getTopVC(withCurrentVC: presentVC)
}else if let tabVC = VC as? UITabBarController {
// tabBar 的跟控制器
if let selectVC = tabVC.selectedViewController {
return getTopVC(withCurrentVC: selectVC)
}
return nil
} else if let naiVC = VC as? UINavigationController {
// 控制器是 nav
return getTopVC(withCurrentVC:naiVC.visibleViewController)
} else {
// 返回顶控制器
return VC
}
}
// 获取window
fileprivate func getWindow() -> UIWindow? {
var window: UIWindow? = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
// 是否为当前显示的window
if window?.windowLevel != UIWindow.Level.normal{
let windows = UIApplication.shared.windows
for windowTemp in windows{
if windowTemp.windowLevel == UIWindow.Level.normal{
window = windowTemp
break
}
}
}
return window
}
}
- 三、
UITableView
的折叠效果
class FSLogViewController: UIViewController {
var sources: [FSLogModel] = FSLogItem.shared.sources
var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
initLogController()
logView()
}
}
extension FSLogViewController {
fileprivate func initLogController() {
view.backgroundColor = .purple
view.layer.cornerRadius = 10
view.layer.masksToBounds = true
}
fileprivate func logView() {
let screenWidth = UIScreen.main.bounds.width
let topView = FSLogTopView()
topView.frame = CGRect(x: 0, y: 0, width: screenWidth - 60, height: 50)
topView.delegate = self
view.addSubview(topView)
tableView = UITableView(frame: CGRect(x: 0, y: 50, width: UIScreen.main.bounds.width - 60, height: view.frame.height - 50), style: .grouped)
tableView.backgroundColor = .white
tableView.delegate = self
tableView.dataSource = self
tableView.tableFooterView = UIView()
tableView.separatorStyle = .none
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 150, right: 0)
tableView.estimatedRowHeight = 40
view.addSubview(tableView)
}
}
extension FSLogViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return sources.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sources[section].isFlod ? sources[section].contents.count : 0
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 44
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let model: FSLogModel = sources[indexPath.section]
return model.cellHeight
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = FSLogTableViewCell(style: .default, reuseIdentifier: "FSLogTableCellID")
let model: FSLogModel = sources[indexPath.section]
model.cellHeight = model.autoHeight(model.contents[indexPath.row])
cell.config(model, indexPath)
return cell
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = FSLogHeaderView.headerView(self.tableView)
header.config(sources[section])
header.delegate = self
header.section = section
return header
}
}
extension FSLogViewController: FSLogHeaderViewDelegate {
func viewMoreRequestContents(_ header: FSLogHeaderView, section: Int) {
let flod = sources[section].isFlod
sources[section].isFlod = !flod!
let index = IndexSet(integer: section)
self.tableView.reloadSections(index, with: .fade)
}
}
extension FSLogViewController: FSLogTopViewDelegate {
func dismiss() {
FSLogManager.shared.dismiss()
}
func clear() {
sources = []
FSLogItem.shared.sources = []
tableView.reloadData()
}
}
-
效果图
日志上报组件
网友评论