Swift 寫(xiě)一個(gè)日志上報(bào)組件

思路

為了方便測(cè)試,開(kāi)發(fā)者們一般都會(huì)寫(xiě)個(gè)日志上報(bào)的組件,通過(guò)上報(bào)的內(nèi)容,很方便檢查接口的情況。首先我們需要定義一個(gè)只有測(cè)試環(huán)境和預(yù)發(fā)布環(huán)境才能顯示的日志浮窗,通過(guò)點(diǎn)擊浮窗顯示一個(gè)日志上報(bào)的列表彈窗。UITableView的組頭我們添加一個(gè)手勢(shì),實(shí)現(xiàn)cell的折疊效果,當(dāng)cell展開(kāi)時(shí),顯示上報(bào)的日志內(nèi)容。cell的大小根據(jù)內(nèi)容來(lái)決定,并實(shí)現(xiàn)日志請(qǐng)求發(fā)至企業(yè)微信、復(fù)制、清除、關(guān)閉日志彈窗等功能。

  • 一、浮窗按鈕
    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()
    
    // 動(dòng)畫(huà)時(shí)長(zhǎng)
    private var duration: TimeInterval = 0.25;
    
    // 彈窗內(nèi)容
    var contentView: UIView?
    
    // 是否允許點(diǎn)擊陰影消失
    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()
    }
    
    //顯示動(dòng)畫(huà)
    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
        }
    }
    
    // 隱藏動(dòng)畫(huà)
    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 {

    // 獲取頂層控制器 根據(jù)window
    fileprivate func getTopVC() -> (UIViewController?) {
        let window = self.getWindow()
        let vc = window?.rootViewController
        return getTopVC(withCurrentVC: vc)
    }
    ///根據(jù)控制器獲取 頂層控制器
    fileprivate func getTopVC(withCurrentVC VC :UIViewController?) -> UIViewController? {
        if VC == nil {
            print("【日志上報(bào)】找不到頂層控制器")
            return nil
        }
        if let presentVC = VC?.presentedViewController {
            //modal出來(lái)的 控制器
            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
        
        // 是否為當(dāng)前顯示的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()
    }
}

  • 效果圖


    日志上報(bào)組件
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容