jsdw_ios/QuickLocation/Section/Home/PhoneReportDetail/PhoneReportDetailView.swift

748 lines
28 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// PhoneReportDetailView.swift
// QuickLocation
//
import UIKit
import Kingfisher
final class PhoneReportDetailView: UIView {
lazy var navView: BaseNavigationView = {
let nav = BaseNavigationView(title: "手机报告")
return nav
}()
lazy var navBgView: UIImageView = {
let iv = UIImageView()
iv.image = UIImage(named: "Common/navBar_bg_2")
iv.contentMode = .scaleAspectFill
return iv
}()
let switchGroupBtn: UIButton = {
let btn = UIButton(type: .custom)
btn.backgroundColor = .white.withAlphaComponent(0.8)
btn.setTitle(" 切换圈子 ", for: .normal)
btn.titleLabel?.font = .systemFont(ofSize: 12, weight: .medium)
btn.setTitleColor(UIColor(hexStr: "#293445"), for: .normal)
btn.cornerRadius = 8
btn.extendEdgeInsets = UIEdgeInsets(top: 20, left: 20, bottom: 15, right: 18)
return btn
}()
lazy var memberCV: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.itemSize = CGSize(width: 61, height: 90)
layout.minimumLineSpacing = 12
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .clear
cv.showsHorizontalScrollIndicator = false
cv.contentInset = UIEdgeInsets(top: 5, left: 5, bottom: 0, right: 5)
cv.register(GroupMemberListCell.self)
// cv.applyPhoneReportCardStyle(cornerRadius: 20)
return cv
}()
let scrollView = UIScrollView()
let contentView = UIView()
private let memberTrailingFadeView = PhoneReportMemberTrailingFadeView()
// MARK: Status
let statusTitleIcon = UIImageView(image: UIImage(named: "Home/report_phone"))
let statusTitleLab = UILabel()
let statusCard = UIView()
let batteryRing = PhoneBatteryRingView()
let estimateLab = UILabel()
let brightnessCard = PhoneStatusMiniCard(iconName: "Home/phone_brightness", title: "亮度")
let volumeCard = PhoneStatusMiniCard(iconName: "Home/phone_volume", title: "音量")
let wifiCard = PhoneStatusMiniCard(iconName: "Home/phone_wifi", title: "WIFI")
let deviceCard = UIView()
let deviceNameLab = UILabel()
let storageLab = UILabel()
let storageBarTrack = UIView()
let storageBar = UIProgressView(progressViewStyle: .default)
let deviceImage = UIImageView(image: UIImage(named: "Home/phone_device"))
let brandLogoImage = UIImageView()
private var brandLogoURL = ""
private let statusDeniedOverlay = PrivilegeDeniedOverlay()
var onRemind: (() -> Void)?
// MARK: Charts
let screenSection = PhoneReportChartSectionView(
title: "屏幕使用时间",
iconName: "Home/report_screen",
summaryPrefix: "今日使用时长",
summaryColor: UIColor(hexStr: "#00ADFE"),
barGradient: (UIColor(hexStr: "#C0EAFF"), UIColor(hexStr: "#ECF9FF")),
highlightGradient: (UIColor(hexStr: "#16B3FF"), UIColor(hexStr: "#9EDFFF")),
tipColor: UIColor(hexStr: "#20B6FF"),
tipArrowImageName: "Home/report_screen_tip_arrow"
)
let unlockSection = PhoneReportChartSectionView(
title: "APP使用次数",
iconName: "Home/report_usage",
summaryPrefix: "今日使用次数",
summaryColor: UIColor(hexStr: "#68C025"),
barGradient: (UIColor(hexStr: "#DDFFA1"), UIColor(hexStr: "#F2FFDC")),
highlightGradient: (UIColor(hexStr: "#AAF52A"), UIColor(hexStr: "#E8FFC1")),
tipColor: UIColor(hexStr: "#86E73D"),
tipArrowImageName: "Home/report_usage_tip_arrow",
valueSuffix: ""
)
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor(hexStr: "#FAFAFA")
setupUI()
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
private func setupUI() {
addSubview(navBgView)
addSubview(navView)
navBgView.layoutChain
.edges(excludingEdge: .bottom)
.heightToWidth(160/375)
navView.layoutChain.top().edgesHorzontal().height(kNaviHeight)
// navView.addRightButton(switchGroupBtn)
addSubview(scrollView)
scrollView.showsVerticalScrollIndicator = false
scrollView.clipsToBounds = true
scrollView.delaysContentTouches = false
scrollView.canCancelContentTouches = true
scrollView.layoutChain
.topToBottomOfView(navView)
.edgesHorzontal()
.bottom()
scrollView.addSubview(contentView)
contentView.layoutChain.edges().widthToView(scrollView)
let memberView = UIView()
memberView.backgroundColor = .white
memberView.applyPhoneReportCardStyle(cornerRadius: 20)
contentView.addSubview(memberView)
memberView.addSubview(memberCV)
memberView.addSubview(memberTrailingFadeView)
memberView.layoutChain
.top(8)
.edgesHorzontal(15)
.height(100)
memberCV.layoutChain
.edgesVertical()
.edgesHorzontal()
memberTrailingFadeView.layoutChain
.top()
.right()
.bottom()
.width(52)
statusTitleIcon.contentMode = .scaleAspectFit
contentView.addSubview(statusTitleIcon)
statusTitleIcon.layoutChain
.topToBottomOfView(memberCV, offset: 12)
.left(15)
.width(18)
.height(18)
statusTitleLab.text = "手机状态"
statusTitleLab.font = .systemFont(ofSize: 16, weight: .bold)
statusTitleLab.textColor = UIColor(hexStr: "#293445")
contentView.addSubview(statusTitleLab)
statusTitleLab.layoutChain
.centerY(statusTitleIcon)
.leftToRightOfView(statusTitleIcon, offset: 6)
statusCard.backgroundColor = .white
statusCard.applyPhoneReportCardStyle(cornerRadius: 20)
contentView.addSubview(statusCard)
statusCard.layoutChain
.topToBottomOfView(statusTitleIcon, offset: 10)
.edgesHorzontal(15)
.height(175)
statusCard.addSubview(statusDeniedOverlay)
statusDeniedOverlay.layoutChain.edges()
statusDeniedOverlay.layer.cornerRadius = 20
let leftPanel = UIView()
leftPanel.backgroundColor = .clear
leftPanel.cornerRadius = 14
statusCard.addSubview(leftPanel)
leftPanel.layoutChain
.edges(excludingEdge: .right)
.width(136)
leftPanel.addSubview(batteryRing)
batteryRing.layoutChain.top(23).centerX().width(100).height(100)
estimateLab.font = .systemFont(ofSize: 12, weight: .medium)
estimateLab.textColor = UIColor(hexStr: "#293445")
estimateLab.textAlignment = .center
estimateLab.backgroundColor = UIColor(hexStr: "#FAFAFA")
estimateLab.cornerRadius = 5
leftPanel.addSubview(estimateLab)
estimateLab.layoutChain
.topToBottomOfView(batteryRing, offset: 12)
.centerX()
.height(22)
.width(120)
let rightStack = UIStackView(arrangedSubviews: [brightnessCard, volumeCard, wifiCard])
rightStack.axis = .horizontal
rightStack.distribution = .fillEqually
rightStack.spacing = 8
statusCard.addSubview(rightStack)
rightStack.layoutChain
.top(12)
.leftToRightOfView(leftPanel, offset: 10)
.right(12)
.height(64)
deviceCard.backgroundColor = UIColor(hexStr: "#FAFAFA")
deviceCard.cornerRadius = 12
statusCard.addSubview(deviceCard)
deviceCard.layoutChain
.topToBottomOfView(rightStack, offset: 8)
.leftToView(rightStack)
.right(12)
.bottom(12)
deviceNameLab.font = .systemFont(ofSize: 13, weight: .bold)
deviceNameLab.textColor = UIColor(hexStr: "#293445")
deviceCard.addSubview(deviceNameLab)
deviceNameLab.layoutChain.top(10).left(10).right(60)
storageLab.font = .systemFont(ofSize: 11, weight: .medium)
storageLab.textColor = UIColor(hexStr: "#6B7280")
deviceCard.addSubview(storageLab)
storageLab.layoutChain.topToBottomOfView(deviceNameLab, offset: 4).left(10)
storageBarTrack.backgroundColor = UIColor(hexStr: "#E0E0E0")
storageBarTrack.layer.cornerRadius = 5
storageBarTrack.clipsToBounds = true
deviceCard.addSubview(storageBarTrack)
storageBarTrack.layoutChain
.topToBottomOfView(storageLab, offset: 6)
.left(10)
.right(60)
.height(10)
.bottom(10)
storageBar.progressTintColor = UIColor(hexStr: "#16B3FF")
storageBar.trackTintColor = UIColor(hexStr: "#16B3FF", alpha: 0.3)
storageBarTrack.addSubview(storageBar)
storageBar.layoutChain
.edgesHorzontal(3)
.centerY()
deviceImage.contentMode = .scaleAspectFit
deviceCard.addSubview(deviceImage)
deviceImage.layoutChain.right(8).centerY().width(36).height(54)
brandLogoImage.contentMode = .scaleAspectFit
brandLogoImage.isHidden = true
deviceImage.addSubview(brandLogoImage)
brandLogoImage.layoutChain.center().width(18).height(18)
contentView.addSubview(screenSection)
screenSection.layoutChain
.topToBottomOfView(statusCard, offset: 16)
.edgesHorzontal(15)
contentView.addSubview(unlockSection)
unlockSection.layoutChain
.topToBottomOfView(screenSection, offset: 16)
.edgesHorzontal(15)
.bottom(24)
}
override func layoutSubviews() {
super.layoutSubviews()
updateMemberFadeVisibility()
}
func updateMemberFadeVisibility() {
let rightInset = memberCV.adjustedContentInset.right
let maxOffsetX = max(
-memberCV.adjustedContentInset.left,
memberCV.contentSize.width - memberCV.bounds.width + rightInset
)
memberTrailingFadeView.isHidden = memberCV.contentOffset.x >= maxOffsetX - 1
|| memberCV.contentSize.width <= memberCV.bounds.width
}
func apply(_ snap: PhoneReportDetailViewModel.StatusSnapshot) {
batteryRing.configure(percent: snap.batteryPercent, text: snap.batteryText, isCharging: snap.isCharging)
estimateLab.text = "预计使用\(snap.estimateText)"
brightnessCard.setValue(snap.brightnessText)
volumeCard.setValue(snap.volumeText)
wifiCard.setValue(snap.wifiText)
deviceNameLab.text = snap.deviceText
storageLab.text = snap.storageText
storageBar.progress = Float(snap.storageRatio)
updateBrandLogo(urlString: snap.brandIconURL)
screenSection.setSummary(snap.screenTodayText)
unlockSection.setSummary(snap.appUsageTodayText)
screenSection.setBars(snap.screenBars)
unlockSection.setBars(snap.appUsageBars)
}
func setPrivilegeDenied(_ denied: Bool) {
let title = "TA未开启设置"
let message = "无法查看手机状态"
let action = "提醒TA"
statusDeniedOverlay.configure(
title: title,
message: message,
actionTitle: action,
imageName: "PrivilegeDenied/report_status"
)
statusDeniedOverlay.onAction = { [weak self] in self?.onRemind?() }
statusDeniedOverlay.setVisible(denied)
if denied { statusCard.bringSubviewToFront(statusDeniedOverlay) }
screenSection.setDenied(denied, imageName: "PrivilegeDenied/report_screen", onRemind: { [weak self] in
self?.onRemind?()
})
unlockSection.setDenied(denied, imageName: "PrivilegeDenied/report_usage", onRemind: { [weak self] in
self?.onRemind?()
})
}
private func updateBrandLogo(urlString: String) {
brandLogoURL = urlString
brandLogoImage.kf.cancelDownloadTask()
brandLogoImage.image = nil
brandLogoImage.isHidden = true
guard let url = URL(string: urlString), !urlString.isEmpty else {
return
}
brandLogoImage.kf.setImage(with: url) { [weak self] result in
guard let self, self.brandLogoURL == urlString else { return }
switch result {
case .success:
self.brandLogoImage.isHidden = false
case .failure:
self.brandLogoImage.image = nil
self.brandLogoImage.isHidden = true
}
}
}
}
private extension UIView {
func applyPhoneReportCardStyle(cornerRadius: CGFloat) {
layer.cornerRadius = cornerRadius
clipsToBounds = false
layer.masksToBounds = false
layer.shadowColor = UIColor(hexStr: "#E5E5E5", alpha: 0.3).cgColor
layer.shadowOffset = CGSize(width: 0, height: 0)
layer.shadowOpacity = 1
layer.shadowRadius = 5
}
}
// MARK: - Subviews
final class PhoneBatteryRingView: UIView {
private let track = CAShapeLayer()
private let progress = CAShapeLayer()
private let valueLab = UILabel()
private let bolt = UIImageView(image: UIImage(named: "Home/report_bolt"))
override init(frame: CGRect) {
super.init(frame: frame)
track.fillColor = UIColor.clear.cgColor
track.strokeColor = UIColor(hexStr: "#DEFFD8").cgColor
track.lineWidth = 11
layer.addSublayer(track)
progress.fillColor = UIColor.clear.cgColor
progress.strokeColor = UIColor(hexStr: "#27DB4B").cgColor
progress.lineWidth = 11
progress.lineCap = .round
progress.strokeEnd = 0
layer.addSublayer(progress)
bolt.contentMode = .scaleAspectFit
addSubview(bolt)
valueLab.font = .systemFont(ofSize: 16, weight: .heavy)
valueLab.textColor = UIColor(hexStr: "#293445")
valueLab.textAlignment = .center
addSubview(valueLab)
valueLab.layoutChain.centerX().centerY()
bolt.layoutChain
.centerY(valueLab)
.rightToLeftOfView(valueLab, offset: -2)
.width(12)
.height(14)
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
override func layoutSubviews() {
super.layoutSubviews()
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let radius = min(bounds.width, bounds.height) / 2 - 6
let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: -.pi / 2, endAngle: 1.5 * .pi, clockwise: true)
track.path = path.cgPath
progress.path = path.cgPath
}
func configure(percent: Int?, text: String, isCharging: Bool = false) {
valueLab.text = text
bolt.isHidden = !isCharging
progress.strokeEnd = CGFloat(max(0, min(100, percent ?? 0))) / 100
}
}
final class PhoneStatusMiniCard: UIView {
private let icon = UIImageView()
private let titleLab = UILabel()
private let valueLab = UILabel()
init(iconName: String, title: String) {
super.init(frame: .zero)
backgroundColor = UIColor(hexStr: "#FAFAFA")
cornerRadius = 12
icon.image = UIImage(named: iconName)
icon.contentMode = .scaleAspectFit
addSubview(icon)
icon.layoutChain.top(7).left(5).width(16).height(16)
titleLab.text = title
titleLab.font = .systemFont(ofSize: 10, weight: .medium)
titleLab.textColor = UIColor(hexStr: "#AAAAAA")
titleLab.textAlignment = .center
addSubview(titleLab)
titleLab.layoutChain.leftToRightOfView(icon, offset: 4).right(5, relation: .greaterThanOrEqual).centerY(icon)
valueLab.font = .systemFont(ofSize: 16, weight: .medium)
valueLab.textColor = UIColor(hexStr: "#293445")
valueLab.textAlignment = .center
addSubview(valueLab)
valueLab.layoutChain.topToBottomOfView(icon, offset: 10).edgesHorzontal(2)
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
func setValue(_ text: String) {
valueLab.text = text
}
}
final class PhoneReportChartSectionView: UIView {
let summaryPrefix: String
private let titleLab = UILabel()
private let summaryPrefixLab = UILabel()
private let summaryLab = UILabel()
private let chart = PhoneReportBarChartView()
private let summaryColor: UIColor
private let barGradient: (UIColor, UIColor)
private let highlightGradient: (UIColor, UIColor)
private let tipColor: UIColor
private let tipArrowImageName: String
private let valueSuffix: String
private let card = UIView()
private let deniedOverlay = PrivilegeDeniedOverlay()
init(
title: String,
iconName: String,
summaryPrefix: String,
summaryColor: UIColor,
barGradient: (UIColor, UIColor),
highlightGradient: (UIColor, UIColor),
tipColor: UIColor,
tipArrowImageName: String,
valueSuffix: String = ""
) {
self.summaryPrefix = summaryPrefix
self.summaryColor = summaryColor
self.barGradient = barGradient
self.highlightGradient = highlightGradient
self.tipColor = tipColor
self.tipArrowImageName = tipArrowImageName
self.valueSuffix = valueSuffix
super.init(frame: .zero)
let titleIcon = UIImageView(image: UIImage(named: iconName))
titleIcon.contentMode = .scaleAspectFit
addSubview(titleIcon)
titleIcon.layoutChain.top().left().width(18).height(18)
titleLab.text = title
titleLab.font = .systemFont(ofSize: 16, weight: .bold)
titleLab.textColor = UIColor(hexStr: "#293445")
addSubview(titleLab)
titleLab.layoutChain.centerY(titleIcon).leftToRightOfView(titleIcon, offset: 6)
card.backgroundColor = .white
card.applyPhoneReportCardStyle(cornerRadius: 20)
addSubview(card)
card.layoutChain.topToBottomOfView(titleLab, offset: 10).edgesHorzontal().bottom().height(180)
card.addSubview(deniedOverlay)
deniedOverlay.layoutChain.edges()
deniedOverlay.layer.cornerRadius = 20
summaryPrefixLab.text = summaryPrefix
summaryPrefixLab.font = .systemFont(ofSize: 12, weight: .medium)
summaryPrefixLab.textColor = UIColor(hexStr: "#293445")
summaryPrefixLab.setContentCompressionResistancePriority(.required, for: .horizontal)
card.addSubview(summaryPrefixLab)
summaryPrefixLab.layoutChain.top(14).left(14)
summaryLab.font = .systemFont(ofSize: 12, weight: .bold)
summaryLab.textColor = summaryColor
summaryLab.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
card.addSubview(summaryLab)
summaryLab.layoutChain
.centerY(summaryPrefixLab)
.leftToRightOfView(summaryPrefixLab, offset: 4)
.right(14, relation: .greaterThanOrEqual)
card.addSubview(chart)
chart.layoutChain.topToBottomOfView(summaryPrefixLab, offset: 16).edgesHorzontal(25).bottom(14)
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
func setDenied(_ denied: Bool, imageName: String, onRemind: (() -> Void)?) {
deniedOverlay.configure(
title: "TA未开启设置",
message: "无法查看手机状态",
actionTitle: "提醒TA",
imageName: imageName
)
deniedOverlay.onAction = onRemind
deniedOverlay.setVisible(denied)
if denied { card.bringSubviewToFront(deniedOverlay) }
}
func setSummary(_ text: String) {
summaryLab.text = text
}
func setBars(_ bars: [PhoneReportDetailViewModel.ChartBar]) {
chart.configure(
bars: bars,
barGradient: barGradient,
highlightGradient: highlightGradient,
tipColor: tipColor,
tipArrowImageName: tipArrowImageName,
suffix: valueSuffix
)
}
}
final class PhoneReportBarChartView: UIView {
private var bars: [PhoneReportDetailViewModel.ChartBar] = []
private var barGradient: (UIColor, UIColor) = (.systemBlue, .systemBlue)
private var highlightGradient: (UIColor, UIColor) = (.systemBlue, .systemBlue)
private var tipColor: UIColor = .systemBlue
private var suffix = ""
private var barLayers: [CALayer] = []
private var labelViews: [UILabel] = []
private var barFrames: [CGRect] = []
private var selectedIndex: Int?
private var barSpacing: CGFloat = 0
private var baselineY: CGFloat = 0
private let tipLab = UILabel()
private let tipArrowView = UIImageView()
private let baseline = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
clipsToBounds = false
isUserInteractionEnabled = true
baseline.backgroundColor = UIColor(hexStr: "#F2F2F2")
addSubview(baseline)
tipLab.font = .systemFont(ofSize: 11, weight: .bold)
tipLab.textColor = .white
tipLab.textAlignment = .center
tipLab.cornerRadius = 6
tipLab.clipsToBounds = true
tipLab.isHidden = true
tipLab.isUserInteractionEnabled = false
addSubview(tipLab)
tipArrowView.contentMode = .scaleAspectFit
tipArrowView.isHidden = true
tipArrowView.isUserInteractionEnabled = false
addSubview(tipArrowView)
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
addGestureRecognizer(tap)
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
func configure(
bars: [PhoneReportDetailViewModel.ChartBar],
barGradient: (UIColor, UIColor),
highlightGradient: (UIColor, UIColor),
tipColor: UIColor,
tipArrowImageName: String,
suffix: String
) {
let previous = self.bars
self.bars = bars
self.barGradient = barGradient
self.highlightGradient = highlightGradient
self.tipColor = tipColor
tipArrowView.image = UIImage(named: tipArrowImageName)
self.suffix = suffix
let dataChanged = previous.count != bars.count
|| zip(previous, bars).contains { $0.label != $1.label || $0.value != $1.value }
if dataChanged || selectedIndex == nil {
selectedIndex = bars.firstIndex(where: \.isToday) ?? (bars.isEmpty ? nil : bars.count - 1)
}
setNeedsLayout()
}
@objc private func handleTap(_ gesture: UITapGestureRecognizer) {
let point = gesture.location(in: self)
guard !barFrames.isEmpty else { return }
for (i, frame) in barFrames.enumerated() {
let hitX = frame.minX - barSpacing / 2
let hitW = frame.width + barSpacing
let hit = CGRect(x: hitX, y: 0, width: hitW, height: max(baselineY, bounds.height))
if hit.contains(point) {
guard selectedIndex != i else { return }
selectedIndex = i
setNeedsLayout()
return
}
}
}
override func layoutSubviews() {
super.layoutSubviews()
barLayers.forEach { $0.removeFromSuperlayer() }
barLayers.removeAll()
labelViews.forEach { $0.removeFromSuperview() }
labelViews.removeAll()
barFrames.removeAll()
tipLab.isHidden = true
tipArrowView.isHidden = true
guard !bars.isEmpty else {
baseline.isHidden = true
return
}
baseline.isHidden = false
let maxVal = max(bars.map(\.value).max() ?? 1, 1)
let count = bars.count
let labelH: CGFloat = 16
let tipVerticalPadding: CGFloat = 8
let tipArrowHeight: CGFloat = 5
let tipArrowBarSpacing: CGFloat = 6
let tipHeight = ceil(tipLab.font.lineHeight) + tipVerticalPadding
let chartH = max(4, bounds.height - labelH - tipHeight - tipArrowHeight - tipArrowBarSpacing)
let barW: CGFloat = 16
barSpacing = count > 1
? max(0, (bounds.width - barW * CGFloat(count)) / CGFloat(count - 1))
: 0
baselineY = bounds.height - labelH
baseline.frame = CGRect(x: 0, y: baselineY - 0.5, width: bounds.width, height: 1)
if selectedIndex == nil || selectedIndex! >= count {
selectedIndex = bars.firstIndex(where: \.isToday) ?? count - 1
}
for (i, bar) in bars.enumerated() {
let ratio = CGFloat(bar.value) / CGFloat(maxVal)
let h = max(4, chartH * max(0.05, ratio))
let x = CGFloat(i) * (barW + barSpacing)
let y = baselineY - h
let barFrame = CGRect(x: x, y: y, width: barW, height: h)
barFrames.append(barFrame)
let isSelected = i == selectedIndex
let gradient = CAGradientLayer()
gradient.frame = barFrame
let colors = isSelected ? highlightGradient : barGradient
gradient.colors = [colors.0.cgColor, colors.1.cgColor]
gradient.startPoint = CGPoint(x: 0.5, y: 0)
gradient.endPoint = CGPoint(x: 0.5, y: 1)
gradient.cornerRadius = 4
gradient.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
layer.insertSublayer(gradient, below: tipLab.layer)
barLayers.append(gradient)
let lab = UILabel()
lab.text = bar.label
lab.font = .systemFont(ofSize: 10, weight: .medium)
lab.textColor = UIColor(hexStr: "#9CA3AF")
lab.textAlignment = .center
lab.frame = CGRect(x: x - 10, y: bounds.height - labelH, width: barW + 20, height: labelH)
insertSubview(lab, belowSubview: tipLab)
labelViews.append(lab)
if isSelected {
tipLab.text = tipText(for: bar)
tipLab.backgroundColor = tipColor
tipLab.sizeToFit()
let tipW = tipLab.bounds.width + 5 + 5
// chart
tipLab.frame = CGRect(
x: x + barW / 2 - tipW / 2,
y: max(0, y - tipHeight - tipArrowHeight - tipArrowBarSpacing),
width: tipW,
height: tipHeight
)
tipLab.isHidden = false
tipArrowView.frame = CGRect(
x: x + barW / 2 - 5,
y: tipLab.frame.maxY,
width: 10,
height: tipArrowHeight
)
tipArrowView.isHidden = false
}
}
bringSubviewToFront(tipLab)
bringSubviewToFront(tipArrowView)
}
private func tipText(for bar: PhoneReportDetailViewModel.ChartBar) -> String {
if suffix == "" {
return "\(bar.value)"
}
let hours = bar.value / 3600
let mins = (bar.value % 3600) / 60
if hours > 0 {
return mins > 0 ? "\(hours)小时\(mins)" : "\(hours)小时"
}
return "\(mins)分钟"
}
}
private final class PhoneReportMemberTrailingFadeView: UIView {
override class var layerClass: AnyClass { CAGradientLayer.self }
override init(frame: CGRect) {
super.init(frame: frame)
isUserInteractionEnabled = false
let gradientLayer = layer as! CAGradientLayer
gradientLayer.colors = [
UIColor.white.withAlphaComponent(0).cgColor,
UIColor.white.withAlphaComponent(0.88).cgColor,
UIColor.white.cgColor
]
gradientLayer.locations = [0, 0.7, 1]
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)
gradientLayer.cornerRadius = 20
gradientLayer.maskedCorners = [.layerMaxXMinYCorner, .layerMaxXMaxYCorner]
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}