Swift 写一个日志上报组件

2024-05-21  本文已影响0人  Alexander
思路

为了方便测试,开发者们一般都会写个日志上报的组件,通过上报的内容,很方便检查接口的情况。首先我们需要定义一个只有测试环境和预发布环境才能显示的日志浮窗,通过点击浮窗显示一个日志上报的列表弹窗。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
    }
}


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()
    }
}

上一篇下一篇

猜你喜欢

热点阅读