思路
為了方便測(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)組件