// // GroupMemberView2.swift // QuickLocation // // Created by 八条 on 2026/8/4. // import UIKit import Network import CoreLocation import SwiftyUserDefaults class GroupMemberView2: UIView { private var navBarHeightConstraint: NSLayoutConstraint? private var currentMemberModel: GroupMemberModel? private static let batteryNormalColor = UIColor(hexStr: "#47DD00") private static let batteryLowColor = UIColor(hexStr: "#F85A5D") private static let batteryDefaultTextColor = UIColor(hexStr: "#353B4F") /// 24pt 图标内腔可用填充宽度(去掉描边与右侧电极) private static let batteryFillMaxWidth: CGFloat = 18 /// 最大化时展开导航占位为 kNaviHeight,收起/dismiss 时为 0 func setNavBarExpanded(_ expanded: Bool) { navBarHeightConstraint?.constant = expanded ? kNaviHeight : 0 } func setupMemberInfo(_ model: GroupMemberModel, isOwner: Bool) { currentMemberModel = model memberNameLab.text = model.nick_name ownView.isHidden = !isOwner statusDotView.backgroundColor = UIColor(hexStr: model.is_online ? "#67EA76" : "#D8D8D8") statusLab.text = model.is_online ? "在线" : "离线" locationLab.text = model.lastLocation let isCurrentUser = model.user_id == AppContextManager.shared.userId updateDistance(for: model, isCurrentUser: isCurrentUser) if isCurrentUser { setupCurrentUserDeviceInfo() } else { setupUnknownDeviceInfo(battery: model.battery) } refreshMemberReports(for: model) } private func updateDistance(for model: GroupMemberModel, isCurrentUser: Bool) { guard !isCurrentUser else { distanceView.isHidden = true return } guard let lat = Defaults[\.currentLatitude], let lon = Defaults[\.currentLongitude] else { distanceView.isHidden = true return } let meCoord = CLLocationCoordinate2D(latitude: lat, longitude: lon) let (otherCoord, _) = CircleMember.parsePosition(model.last_position) guard CLLocationCoordinate2DIsValid(meCoord), CLLocationCoordinate2DIsValid(otherCoord) else { distanceView.isHidden = true return } let meters = CLLocation(latitude: meCoord.latitude, longitude: meCoord.longitude) .distance(from: CLLocation(latitude: otherCoord.latitude, longitude: otherCoord.longitude)) distanceLab.text = Self.formatDistance(meters) distanceView.isHidden = false } private static func formatDistance(_ meters: CLLocationDistance) -> String { if meters < 1000 { return "\(Int(meters.rounded()))M" } return String(format: "%.1fKM", meters / 1000.0) } private func setupCurrentUserDeviceInfo() { phoneModelIcon.image = UIImage(named: "Home/member_phone") phoneModelLab.text = Self.localPhoneModelName() phoneModelLab.textColor = Self.batteryDefaultTextColor networkIcon.image = UIImage(named: "Home/member_wifi") networkLab.text = Self.localNetworkStatusText() networkLab.textColor = Self.batteryDefaultTextColor applyBatteryDisplay(percent: Self.localBatteryPercent()) } private func setupUnknownDeviceInfo(battery: String) { phoneModelIcon.image = UIImage(named: "Home/member_phone_unknown") phoneModelLab.text = "***" phoneModelLab.textColor = Self.batteryDefaultTextColor networkIcon.image = UIImage(named: "Home/member_network_unknown") networkLab.text = "***" networkLab.textColor = Self.batteryDefaultTextColor // MQTT / 接口已有电量则展示,否则 unknown let percent = Int(battery.int) if percent > 0 { applyBatteryDisplay(percent: min(percent, 100)) } else { applyBatteryUnknown() } } private func applyBatteryUnknown() { batteryIcon.image = UIImage(named: "Home/member_battery_unknown") batteryFillView.isHidden = true batteryFillView.layoutChain.width(0) batteryLab.text = "***" batteryLab.textColor = Self.batteryDefaultTextColor } private func applyBatteryDisplay(percent: Int?) { guard let percent else { applyBatteryUnknown() return } let p = min(max(percent, 0), 100) let isLow = p < 20 let color = isLow ? Self.batteryLowColor : Self.batteryNormalColor batteryIcon.image = UIImage(named: isLow ? "Home/member_battery_low" : "Home/member_battery") batteryFillView.isHidden = false batteryFillView.backgroundColor = color batteryFillView.layoutChain.width(Self.batteryFillMaxWidth * CGFloat(p) / 100.0) batteryLab.text = "\(p)%" batteryLab.textColor = color } // MARK: - Local device helpers private static func localPhoneModelName() -> String { var systemInfo = utsname() uname(&systemInfo) let identifier = withUnsafePointer(to: &systemInfo.machine.0) { ptr in String(cString: ptr) } return deviceModelMap[identifier] ?? "iPhone" } private static func localBatteryPercent() -> Int? { UIDevice.current.isBatteryMonitoringEnabled = true let level = UIDevice.current.batteryLevel guard level >= 0 else { return nil } return Int((level * 100).rounded()) } private static func localNetworkStatusText() -> String { let path = NetworkStatusMonitor.shared.currentPath if path.status != .satisfied { return "无网络" } if path.usesInterfaceType(.wifi) { return "Wi-Fi" } if path.usesInterfaceType(.cellular) { return "蜂窝" } return "无网络" } private static let deviceModelMap: [String: String] = [ "iPhone10,1": "iPhone 8", "iPhone10,4": "iPhone 8", "iPhone10,2": "iPhone 8 Plus", "iPhone10,5": "iPhone 8 Plus", // iPhone X系列 "iPhone10,3": "iPhone X", "iPhone10,6": "iPhone X", "iPhone11,2": "iPhone XS", "iPhone11,4": "iPhone XS Max", "iPhone11,6": "iPhone XS Max", "iPhone11,8": "iPhone XR", // iPhone11 "iPhone12,1": "iPhone 11", "iPhone12,3": "iPhone 11 Pro", "iPhone12,5": "iPhone 11 Pro Max", // iPhone12 "iPhone13,1": "iPhone 12 mini", "iPhone13,2": "iPhone 12", "iPhone13,3": "iPhone 12 Pro", "iPhone13,4": "iPhone 12 Pro Max", // iPhone13 "iPhone14,4": "iPhone 13 mini", "iPhone14,5": "iPhone 13", "iPhone14,2": "iPhone 13 Pro", "iPhone14,3": "iPhone 13 Pro Max", // iPhone14 "iPhone14,7": "iPhone 14", "iPhone14,8": "iPhone 14 Plus", "iPhone15,2": "iPhone 14 Pro", "iPhone15,3": "iPhone 14 Pro Max", // iPhone15 "iPhone15,4": "iPhone 15", "iPhone15,5": "iPhone 15 Plus", "iPhone16,1": "iPhone 15 Pro", "iPhone16,2": "iPhone 15 Pro Max", // iPhone16 "iPhone17,1": "iPhone 16 Pro", "iPhone17,2": "iPhone 16 Pro Max", "iPhone17,3": "iPhone 16", "iPhone17,4": "iPhone 16 Plus", "iPhone17,5": "iPhone 16e", // iPhone17 2025机型 "iPhone18,1": "iPhone 17 Pro", "iPhone18,2": "iPhone 17 Pro Max", "iPhone18,3": "iPhone 17", "iPhone18,4": "iPhone Air", // Simulator "i386": "Simulator", "x86_64": "Simulator", "arm64": "Simulator" ] private func setupRx() { NotificationCenter.default.addObserver( self, selector: #selector(handleUnlockCountDidChange), name: .unlockCountDidChange, object: nil ) } @objc private func handleUnlockCountDidChange() { guard let model = currentMemberModel else { return } refreshMemberReports(for: model) } private func refreshMemberReports(for model: GroupMemberModel) { currentMemberModel = model let isCurrentUser = model.user_id == AppContextManager.shared.userId if isCurrentUser { phoneReportView.configure(with: .currentUser()) } else { phoneReportView.configure(with: nil) } } private func setupUI() { addSubview(headerBgView) addSubview(navBarView) addSubview(scrollView) headerBgView.layoutChain .edges(excludingEdge: .bottom) .heightToWidth(160/375) navBarView.layoutChain .edges(excludingEdge: .bottom) .height(0) navBarHeightConstraint = navBarView.jh_constraint( .height, toAttribute: .notAnAttribute, otherView: nil, relation: .equal ) scrollView.layoutChain .topToBottomOfView(navBarView) .edges(excludingEdge: .top) scrollView.isScrollEnabled = false let lineView = UIView() lineView.backgroundColor = .black.withAlphaComponent(0.2) lineView.cornerRadius = 2 addSubview(lineView) lineView.layoutChain .top(9) .centerX() .width(40) .height(4) } lazy var headerBgView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "Home/group_member_bg2") view.backgroundColor = .clear view.contentMode = .scaleAspectFill return view }() lazy var navBarView: UIView = { let view = UIView() view.backgroundColor = .clear view.clipsToBounds = true let icon = UIImageView(image: UIImage(named: "Home/member_logo")) view.addSubview(icon) icon.layoutChain .top(kStatusBarHeight + 12) .left(30) view.addSubview(inviteBtn) inviteBtn.layoutChain .centerY(icon) .right(15) .width(32) .heightToWidth(1) return view }() lazy var inviteBtn: UIButton = { let btn = UIButton(type: .custom) btn.setImage(UIImage(named: "Home/member_invite"), for: .normal) btn.extendEdgeInsets = UIEdgeInsets(top: 20, left: 20, bottom: 10, right: 15) return btn }() lazy var scrollView: UIScrollView = { let view = UIScrollView() view.backgroundColor = .clear view.showsVerticalScrollIndicator = false view.bounces = false view.addSubview(scrollContentView) scrollContentView.layoutChain.edges().widthToView(view) scrollContentView.addSubview(memberView) memberView.layoutChain .edges(excludingEdge: .bottom) scrollContentView.addSubview(todayTrackView) todayTrackView.layoutChain .topToBottomOfView(memberView, offset: 15) .edgesHorzontal() scrollContentView.addSubview(phoneReportView) phoneReportView.layoutChain .topToBottomOfView(todayTrackView, offset: 15) .edgesHorzontal() .bottom(49 + kSafeBottomMargin) return view }() lazy var scrollContentView: UIView = { let view = UIView() view.backgroundColor = .clear return view }() // MARK: - 成员 lazy var memberView: UIView = { let view = UIView() view.backgroundColor = .clear let icon = UIImageView() icon.image = UIImage(named: "Home/group_name_icon") view.addSubview(icon) icon.layoutChain .top(23) .left(15) .width(18) .heightToWidth(1) view.addSubview(groupNameLab) groupNameLab.layoutChain .leftToRightOfView(icon, offset: 6) .centerY(icon) view.addSubview(changeGroupBtn) changeGroupBtn.layoutChain .centerY(icon) .right(15) .height(20) let memberView = UIView() memberView.backgroundColor = .white memberView.cornerRadius = 20 memberView.addSubview(memberCV) view.addSubview(memberView) memberView.layoutChain .topToBottomOfView(icon, offset: 12) .edgesHorzontal(15) .height(100) memberCV.layoutChain .edges(all: 5, excludingEdge: .bottom) .bottom() view.addSubview(memberInfoView) memberInfoView.layoutChain .topToBottomOfView(memberView, offset: 12) .edgesHorzontal(15) .height(150) .bottom() return view }() lazy var groupNameLab: UILabel = { let label = UILabel() label.text = " " label.font = .systemFont(ofSize: 16, weight: .bold) label.textColor = UIColor(hexStr: "#353B4F") return label }() lazy var changeGroupBtn: UIButton = { let btn = UIButton(type: .custom) btn.setTitle("切换圈子 ", for: .normal) btn.setTitleColor(UIColor(hexStr: "#353B4F"), for: .normal) btn.titleLabel?.font = .systemFont(ofSize: 12, weight: .medium) btn.setImage(UIImage(named: "Common/arrow_right"), for: .normal) btn.semanticContentAttribute = .forceRightToLeft btn.extendEdgeInsets = UIEdgeInsets(top: 20, left: 20, bottom: 10, right: 15) return btn }() /// 成员列表 lazy var memberCV: UICollectionView = { let layout = UICollectionViewFlowLayout() layout.itemSize = CGSize(width: 61, height: 95) layout.minimumLineSpacing = 15 layout.sectionInset = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8) layout.scrollDirection = .horizontal let cv = UICollectionView(frame: .zero, collectionViewLayout: layout) cv.backgroundColor = .clear cv.showsHorizontalScrollIndicator = false cv.register(GroupMemberListCell.self) return cv }() /// 成员信息 lazy var memberInfoView: UIView = { let view = UIView() view.backgroundColor = .white view.cornerRadius = 20 view.addSubview(memberNameLab) memberNameLab.layoutChain .top(20) .left(18) view.addSubview(tagView) tagView.layoutChain .leftToRightOfView(memberNameLab, offset: 6) .centerY(memberNameLab) .height(20) ownView.layoutChain.height(16) let statusView = UIView() statusView.backgroundColor = .clear statusView.addSubview(statusDotView) statusView.addSubview(statusLab) view.addSubview(statusView) statusView.layoutChain .right(18) .centerY(memberNameLab) statusDotView.layoutChain .left() .width(8) .heightToWidth(1) .centerY() statusLab.layoutChain .leftToRightOfView(statusDotView, offset: 4) .edgesVertical() .right() let locationIcon = UIImageView() locationIcon.image = UIImage(named: "Home/member_location") view.addSubview(locationIcon) locationIcon.layoutChain .topToBottomOfView(memberNameLab, offset: 17) .left(20) .width(16) .heightToWidth(1) view.addSubview(locationLab) locationLab.layoutChain .centerY(locationIcon) .leftToRightOfView(locationIcon, offset: 7) .compressionHorizontal(.defaultLow) view.addSubview(distanceView) distanceView.layoutChain .right(18) .centerY(locationLab) .compressionHorizontal(.required) locationLab.layoutChain.rightToLeftOfView(distanceView, offset: -30, relation: .lessThanOrEqual) view.addSubview(phoneModelView) view.addSubview(batteryView) view.addSubview(networkView) view.addSubview(weatherView) phoneModelView.layoutChain .topToBottomOfView(locationLab, offset: 20) .left() batteryView.layoutChain .topToView(phoneModelView) .leftToRightOfView(phoneModelView) .widthToView(phoneModelView) networkView.layoutChain .topToView(phoneModelView) .leftToRightOfView(batteryView) .widthToView(phoneModelView) weatherView.layoutChain .topToView(phoneModelView) .leftToRightOfView(networkView) .right() .widthToView(phoneModelView) return view }() lazy var memberNameLab: UILabel = { let label = UILabel() label.text = " " label.font = FontManager.youSheBiaoTiHei(12) label.textColor = UIColor(hexStr: "#353B4F") return label }() lazy var tagView: UIStackView = { let view = UIStackView(arrangedSubviews: [ownView]) view.axis = .horizontal view.alignment = .center view.spacing = 5 view.backgroundColor = .clear return view }() // 圈主 lazy var ownView: UIView = { let view = UIView() view.backgroundColor = UIColor(hexStr: "#E3F6FF") view.cornerRadius = 6 view.isHidden = true let label = UILabel() label.textColor = UIColor(hexStr: "#128CC6") label.font = .systemFont(ofSize: 10, weight: .bold) label.text = "圈主" view.addSubview(label) label.layoutChain .edgesVertical(2) .edgesHorzontal(8) return view }() /// 在线状态 lazy var statusDotView: UIView = { let view = UIView() view.backgroundColor = UIColor(hexStr: "#D8D8D8") view.cornerRadius = 4 return view }() lazy var statusLab: UILabel = { let label = UILabel() label.text = " " label.font = .systemFont(ofSize: 10, weight: .medium) label.textColor = UIColor(hexStr: "#353B4F") return label }() // 位置 lazy var locationLab: UILabel = { let label = UILabel() label.textColor = UIColor(hexStr: "#767676") label.font = .systemFont(ofSize: 12, weight: .medium) return label }() // 距离 lazy var distanceView: UIView = { let view = UIView() view.backgroundColor = UIColor(hexStr: "#353B4F") view.cornerRadius = 6 view.isHidden = true let label = UILabel() label.textColor = UIColor(hexStr: "#58EDFF") label.font = FontManager.youSheBiaoTiHei(10) label.text = "距离" view.addSubview(label) label.layoutChain .left(5) .edgesVertical(3) view.addSubview(distanceLab) distanceLab.layoutChain .leftToRightOfView(label, offset: 2) .right(5) .centerY() return view }() lazy var distanceLab: UILabel = { let label = UILabel() label.textColor = .white label.font = FontManager.youSheBiaoTiHei(10) return label }() /// 手机型号 lazy var phoneModelView: UIView = { let view = UIView() view.backgroundColor = .clear view.addSubview(phoneModelIcon) phoneModelIcon.layoutChain .top() .centerX() .width(24) .height(24) view.addSubview(phoneModelLab) phoneModelLab.layoutChain .topToBottomOfView(phoneModelIcon, offset: 5) .edgesHorzontal(5) .bottom() return view }() lazy var phoneModelIcon: UIImageView = { let view = UIImageView(image: UIImage(named: "Home/member_phone_unknown")) view.contentMode = .scaleAspectFit return view }() lazy var phoneModelLab: UILabel = { let label = UILabel() label.text = "***" label.textColor = UIColor(hexStr: "#353B4F") label.font = .systemFont(ofSize: 12, weight: .medium) label.textAlignment = .center label.numberOfLines = 0 return label }() /// 电量 lazy var batteryView: UIView = { let view = UIView() view.backgroundColor = .clear view.addSubview(batteryFillView) view.addSubview(batteryIcon) batteryIcon.layoutChain .top() .centerX() .width(24) .height(24) // 填充层叠在轮廓下方,内边距避开描边与右侧电极 batteryFillView.layoutChain .centerY(batteryIcon) .leftToView(batteryIcon, offset: 2) .height(12) .width(0) view.addSubview(batteryLab) batteryLab.layoutChain .topToBottomOfView(batteryIcon, offset: 5) .centerX() .bottom() return view }() lazy var batteryFillView: UIView = { let view = UIView() view.backgroundColor = Self.batteryNormalColor view.isHidden = true return view }() lazy var batteryIcon: UIImageView = { let view = UIImageView(image: UIImage(named: "Home/member_battery_unknown")) view.contentMode = .scaleAspectFit return view }() lazy var batteryLab: UILabel = { let label = UILabel() label.text = "***" label.textColor = UIColor(hexStr: "#353B4F") label.font = .systemFont(ofSize: 12, weight: .medium) return label }() /// 手机网络 lazy var networkView: UIView = { let view = UIView() view.backgroundColor = .clear view.addSubview(networkIcon) networkIcon.layoutChain .top() .centerX() .width(24) .height(24) view.addSubview(networkLab) networkLab.layoutChain .topToBottomOfView(networkIcon, offset: 5) .centerX() .bottom() return view }() lazy var networkIcon: UIImageView = { let view = UIImageView(image: UIImage(named: "Home/member_network_unknown")) view.contentMode = .scaleAspectFit return view }() lazy var networkLab: UILabel = { let label = UILabel() label.text = "***" label.textColor = UIColor(hexStr: "#353B4F") label.font = .systemFont(ofSize: 12, weight: .medium) return label }() /// 天气 lazy var weatherView: UIView = { let view = UIView() view.backgroundColor = .clear let icon = UIImageView(image: UIImage(named: "Home/member_weather_unknown")) view.addSubview(icon) icon.layoutChain .top() .centerX() .width(24) .height(24) view.addSubview(weatherLab) weatherLab.layoutChain .topToBottomOfView(icon, offset: 5) .centerX() .bottom() return view }() lazy var weatherLab: UILabel = { let label = UILabel() label.text = "***" label.textColor = UIColor(hexStr: "#353B4F") label.font = .systemFont(ofSize: 12, weight: .medium) return label }() // MARK: - 手机报告 / APP使用记录 lazy var phoneReportView = MemberPhoneReportView() /// 保留 UI 实现,当前不挂载到 scroll 内容 lazy var appUsageView: MemberAppUsageView = { let view = MemberAppUsageView() view.isHidden = true return view }() // MARK: - 今日轨迹 lazy var todayTrackView: UIView = { let view = UIView() view.backgroundColor = .clear let icon = UIImageView() icon.image = UIImage(named: "Home/member_today_track") view.addSubview(icon) icon.layoutChain .top() .left(15) .width(18) .heightToWidth(1) let titleLab = UILabel() titleLab.text = "今日轨迹" titleLab.textColor = UIColor(hexStr: "#293445") titleLab.font = .systemFont(ofSize: 16, weight: .bold) view.addSubview(titleLab) titleLab.layoutChain .leftToRightOfView(icon, offset: 6) .centerY(icon) let infoView = UIView() infoView.backgroundColor = .white infoView.cornerRadius = 16 view.addSubview(infoView) infoView.layoutChain .topToBottomOfView(icon, offset: 12) .edgesHorzontal(15) .bottom() let mapIcon = UIImageView(image: UIImage(named: "Home/member_map")) infoView.addSubview(mapIcon) mapIcon.layoutChain .left(12) .edgesVertical(15) .width(80) .heightToWidth(1) let locationIcon = UIImageView(image: UIImage(named: "Home/member_location")) infoView.addSubview(locationIcon) locationIcon.layoutChain .topToView(mapIcon, offset: 11) .leftToRightOfView(mapIcon, offset: 14) .width(16) .heightToWidth(1) let stayTxtLab = UILabel() stayTxtLab.text = "今天停留了" stayTxtLab.textColor = UIColor(hexStr: "#293445") stayTxtLab.font = .systemFont(ofSize: 16, weight: .medium) view.addSubview(stayTxtLab) stayTxtLab.layoutChain .leftToRightOfView(locationIcon, offset: 6) .centerY(locationIcon) let stayNumView = UIView() stayNumView.backgroundColor = UIColor(hexStr: "#293445") stayNumView.cornerRadius = 6 infoView.addSubview(stayNumView) stayNumView.layoutChain .leftToRightOfView(stayTxtLab) .centerY(stayTxtLab) stayNumView.addSubview(stayNumLab) stayNumLab.layoutChain .edgesHorzontal(5) .edgesVertical() let stayTxtLab1 = UILabel() stayTxtLab1.text = "个地方" stayTxtLab1.textColor = UIColor(hexStr: "#293445") stayTxtLab1.font = .systemFont(ofSize: 16, weight: .medium) view.addSubview(stayTxtLab1) stayTxtLab1.layoutChain .leftToRightOfView(stayNumView, offset: 0) .centerY(stayTxtLab) view.addSubview(stayLocationView) stayLocationView.layoutChain .topToBottomOfView(locationIcon, offset: 12) .leftToRightOfView(mapIcon, offset: 14) .right() .bottomToView(mapIcon) view.addSubview(stayNodataView) stayNodataView.layoutChain .topToBottomOfView(stayTxtLab, offset: 14) .leftToView(stayTxtLab) return view }() lazy var stayNumLab: UILabel = { let label = UILabel() label.text = "0" label.textColor = UIColor(hexStr: "#58EDFF") label.font = FontManager.youSheBiaoTiHei(14) return label }() lazy var stayLocationView: UIView = { let view = UIView() view.backgroundColor = .clear view.isHidden = true let nowDotView = UIView() nowDotView.backgroundColor = UIColor(hexStr: "#3DC0FF") nowDotView.cornerRadius = 6 view.addSubview(nowDotView) nowDotView.layoutChain .top() .left() .width(12) .heightToWidth(1) let dotView = UIView() dotView.backgroundColor = .white dotView.cornerRadius = 3 nowDotView.addSubview(dotView) dotView.layoutChain .centerX() .centerY() .width(6) .heightToWidth(1) let dashLine = DashLineView() dashLine.backgroundColor = .clear view.addSubview(dashLine) dashLine.layoutChain .topToBottomOfView(nowDotView) .centerX(nowDotView) .width(1) .bottom() view.addSubview(stayLocationLab) stayLocationLab.layoutChain .leftToRightOfView(nowDotView, offset: 3) .centerY(nowDotView) .right(15) let stayStatusView = UIView() stayStatusView.backgroundColor = UIColor(hexStr: "#F2F2F2") stayStatusView.cornerRadius = 6 view.addSubview(stayStatusView) stayStatusView.layoutChain .topToBottomOfView(stayLocationLab, offset: 9) .leftToView(stayLocationLab) let clockIcon = UIImageView(image: UIImage(named: "Home/member_clock")) stayStatusView.addSubview(clockIcon) clockIcon.layoutChain .edges(all: 4, excludingEdge: .right) .width(12) .heightToWidth(1) stayStatusView.addSubview(stayStatusLab) stayStatusLab.layoutChain .leftToRightOfView(clockIcon, offset: 2) .right(7) .centerY() return view }() lazy var stayLocationLab: UILabel = { let label = UILabel() label.text = "当前 xxx 附近" label.textColor = UIColor(hexStr: "#293445") label.font = .systemFont(ofSize: 10, weight: .bold) return label }() lazy var stayStatusLab: UILabel = { let label = UILabel() label.text = "停留中..." // 停留多少时间 label.textColor = UIColor(hexStr: "#293445") label.font = .systemFont(ofSize: 8, weight: .bold) return label }() lazy var stayNodataView: UIView = { let view = UIView() view.backgroundColor = UIColor(hexStr: "#F2F2F2") view.cornerRadius = 6 view.isHidden = false let label = UILabel() label.text = "暂时没有停留记录" label.textColor = UIColor(hexStr: "#293445") label.font = .systemFont(ofSize: 12, weight: .medium) view.addSubview(label) label.layoutChain .edgesHorzontal(6) .edgesVertical(9) return view }() override init(frame: CGRect) { super.init(frame: frame) backgroundColor = UIColor(hexStr: "#FAFAFA") cornerRadius = 30 layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] _ = NetworkStatusMonitor.shared setupUI() setupRx() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } deinit { NotificationCenter.default.removeObserver(self) } } /// 轻量网络状态监听,供面板读取当前路径 private final class NetworkStatusMonitor { static let shared = NetworkStatusMonitor() private let monitor = NWPathMonitor() private(set) var currentPath: NWPath private init() { currentPath = monitor.currentPath monitor.pathUpdateHandler = { [weak self] path in self?.currentPath = path } monitor.start(queue: DispatchQueue(label: "com.quicklocation.networkstatus")) } }