QSLHomeFriendView.swift 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  1. //
  2. // QSLHomeFriendView.swift
  3. // QuickSearchLocation
  4. //
  5. // Created by mac on 2024/4/12.
  6. //
  7. import UIKit
  8. import YYText
  9. import SnapKit
  10. enum FriendViewLocation {
  11. case ScrollTop
  12. case ScrollCenter
  13. case ScrollBottom
  14. }
  15. protocol QSLHomeFriendViewDelegate: NSObjectProtocol {
  16. func refreshBtnAction()
  17. func homeFriPhoneViewClick()
  18. func routeBtnAction(model: QSLUserModel)
  19. func locateBtnAction(model: QSLUserModel)
  20. func addFriendAction(isSmall: Bool)
  21. func viewDidScroll()
  22. }
  23. class QSLHomeFriendView: UIView {
  24. weak var delegate: QSLHomeFriendViewDelegate?
  25. // 动态约束处理
  26. var couponViewTopConstraint: Constraint? = nil
  27. private var friBgViewTopConstraint: Constraint? // 新增:保存friBgView的顶部约束
  28. var viewModel:QSLHomeViewModel? {
  29. didSet {
  30. friTableView.reloadData()
  31. }
  32. }
  33. /// 位置
  34. var scrollLocation: FriendViewLocation?
  35. /// 滑动到最后停下来的位置
  36. var scrollStopY: CGFloat?
  37. /// 滑动顶部距离顶部的距离
  38. var scrollTopHeight: CGFloat?
  39. /// 滑动中间距离顶部的距离
  40. var scrollCenterHeight: CGFloat?
  41. /// 滑动底部距离顶部的距离
  42. var scrollBottomHeight: CGFloat?
  43. lazy var friMapLogoImageView: UIImageView = {
  44. let imageView = UIImageView()
  45. imageView.isHidden = true
  46. imageView.image = UIImage(named: "home_friends_map_logo")
  47. return imageView
  48. }()
  49. lazy var friExpandButton: UIButton = {
  50. let button = UIButton(type: .custom)
  51. button.backgroundColor = UIColor.hexStringColor(hexString: "#333333", alpha: 0.6)
  52. button.addRadius(radius: 2)
  53. return button
  54. }()
  55. lazy var refreshButton: UIButton = {
  56. let button = UIButton(type: .custom)
  57. button.setBackgroundImage(UIImage(named: "home_refresh_btn"), for: .normal)
  58. button.addTarget(self, action: #selector(refreshBtnAction), for: .touchUpInside)
  59. return button
  60. }()
  61. lazy var couponView: UIImageView = {
  62. let view = UIImageView()
  63. view.image = UIImage(named: "home_activity_bg")
  64. view.isUserInteractionEnabled = true
  65. view.isHidden = true
  66. let tapG = UITapGestureRecognizer.init(target: self, action: #selector(unlockBtnAction))
  67. view.addGestureRecognizer(tapG)
  68. return view
  69. }()
  70. lazy var couponLabel: YYLabel = {
  71. let label = YYLabel()
  72. return label
  73. }()
  74. lazy var countdownLabel: QSLCountdownView = {
  75. let label = QSLCountdownView(frame: CGRectMake(0, 0, QSLConst.qsl_kScreenW - 24.rpx, 17.rpx),type: 1)
  76. return label
  77. }()
  78. lazy var couponBtn: UILabel = {
  79. let label = UILabel()
  80. label.gradientBackgroundColor(color1: UIColor.hexStringColor(hexString: "#FFFEF3", alpha: 1), color2: UIColor.hexStringColor(hexString: "#FFE6C0", alpha: 1), width: 65.rpx, height: 28.rpx, direction: .horizontal)
  81. label.text("去使用")
  82. label.textAlignment = .center
  83. label.font = UIFont.textM(14)
  84. label.textColor = (UIColor.hexStringColor(hexString: "#3A331C"))
  85. label.addRadius(radius: 14.rpx)
  86. return label
  87. }()
  88. lazy var friBgView: UIView = {
  89. let view = UIView()
  90. view.clipsToBounds = true
  91. view.backgroundColor = QSLColor.backGroundColor
  92. view.addRadius(radius: 12.rpx)
  93. return view
  94. }()
  95. lazy var friHeaderView: UIView = {
  96. let view = UIView(frame: CGRect(x: 0, y: 0, width: QSLConst.qsl_kScreenW, height: 61.rpx))
  97. if let image = UIImage.gradient([UIColor.hexStringColor(hexString: "#FFFFFF", alpha: 1), UIColor.hexStringColor(hexString: "#FFFFFF", alpha: 0)], size: CGSize(width: qsl_kScreenW, height: 61.rpx), locations: [0, 1], direction: .vertical) {
  98. view.backgroundColor = UIColor(patternImage: image)
  99. }
  100. view.addFourCorner(topLeft: 12.rpx, topRight: 12.rpx, bottomLeft: 0, bottomRight: 0)
  101. return view
  102. }()
  103. lazy var friHeaderTitleIcon: UIImageView = {
  104. let imageView = UIImageView()
  105. imageView.image = UIImage(named: "home_friends_header_title")
  106. return imageView
  107. }()
  108. lazy var friHeaderAddBtn: UIButton = {
  109. let btn = UIButton()
  110. btn.setBackgroundImage(UIImage(named: "home_friends_header_add_btn_bg"), for: .normal)
  111. btn.image(UIImage(named: "home_friends_header_add_icon"))
  112. btn.title("查找好友")
  113. btn.mediumFont(15)
  114. btn.textColor(.white)
  115. btn.setImageTitleLayout(.imgLeft, spacing: 0)
  116. btn.addTarget(self, action: #selector(topAddButtonAction), for: .touchUpInside)
  117. return btn
  118. }()
  119. lazy var friTableView: UITableView = {
  120. let tableView = UITableView(frame: .zero, style: .grouped)
  121. tableView.backgroundColor = .clear
  122. tableView.separatorStyle = .none
  123. tableView.showsVerticalScrollIndicator = false
  124. tableView.delegate = self
  125. tableView.dataSource = self
  126. tableView.tableViewNeverAdjustContentInset()
  127. tableView.bounces = false
  128. tableView.isUserInteractionEnabled = true
  129. tableView.isScrollEnabled = false
  130. tableView.contentInsetAdjustmentBehavior = .never
  131. tableView.register(cellClass: QSLHomeFriendTableViewCell.self)
  132. return tableView
  133. }()
  134. override init(frame: CGRect) {
  135. super.init(frame: frame)
  136. self.scrollCenterHeight = self.qsl_top
  137. self.scrollBottomHeight = qsl_kScreenH - qsl_kTabbarFrameH - 56 - 40
  138. self.setupUI()
  139. self.addPanAction()
  140. NotificationCenter.default.addObserver(
  141. self,
  142. selector: #selector(handleCouponNotification(_:)),
  143. name: Notification.Name("QSLHomeUpdateCouponViewNoti"),
  144. object: nil
  145. )
  146. }
  147. required init?(coder: NSCoder) {
  148. fatalError("init(coder:) has not been implemented")
  149. }
  150. @objc func refreshBtnAction() {
  151. delegate?.refreshBtnAction()
  152. }
  153. }
  154. // MARK: - 设置UI
  155. extension QSLHomeFriendView {
  156. func setupUI() {
  157. addSubview(couponView)
  158. couponView.addSubview(couponBtn)
  159. couponView.addSubview(couponLabel)
  160. couponView.addSubview(countdownLabel)
  161. addSubview(friBgView)
  162. addSubview(friTableView)
  163. addSubview(friMapLogoImageView)
  164. addSubview(friExpandButton)
  165. addSubview(refreshButton)
  166. friBgView.addSubview(friHeaderView)
  167. friHeaderView.addSubview(friHeaderTitleIcon)
  168. friHeaderView.addSubview(friHeaderAddBtn)
  169. friMapLogoImageView.snp.makeConstraints { make in
  170. make.size.equalTo(CGSize(width: 62, height: 20))
  171. make.top.equalTo(0)
  172. make.left.equalTo(12)
  173. }
  174. friExpandButton.snp.makeConstraints { make in
  175. make.size.equalTo(CGSize(width: 80, height: 4))
  176. make.top.equalTo(friMapLogoImageView.snp.bottom).offset(8)
  177. make.centerX.equalTo(snp.centerX)
  178. }
  179. // couponView 的约束(初始时设为隐藏)
  180. couponView.snp.makeConstraints { make in
  181. make.left.equalTo(8.rpx)
  182. make.right.equalTo(-8.rpx)
  183. make.height.equalTo(58.rpx)
  184. couponViewTopConstraint = make.top.equalTo(friExpandButton.snp.bottom).offset(8.rpx).constraint
  185. }
  186. couponBtn.snp.makeConstraints { make in
  187. make.height.equalTo(28.rpx)
  188. make.width.equalTo(65.rpx)
  189. make.right.equalTo(-12.rpx)
  190. make.centerY.equalToSuperview()
  191. }
  192. couponLabel.snp.makeConstraints { make in
  193. make.height.equalTo(22.rpx)
  194. make.top.equalTo(10.rpx)
  195. make.left.equalTo(60.rpx)
  196. make.right.equalTo(couponBtn.snp.left).offset(-10.rpx)
  197. }
  198. countdownLabel.snp.makeConstraints { make in
  199. make.height.equalTo(17.rpx)
  200. make.top.equalTo(couponLabel.snp.bottom).offset(0)
  201. make.left.equalTo(60.rpx)
  202. make.right.equalTo(couponBtn.snp.left).offset(-10.rpx)
  203. }
  204. friBgView.snp.makeConstraints { make in
  205. friBgViewTopConstraint = make.top.equalTo(friExpandButton.snp.bottom).offset(8.rpx).constraint
  206. make.left.bottom.right.equalToSuperview()
  207. }
  208. refreshButton.snp.makeConstraints { make in
  209. make.size.equalTo(CGSize(width: 40.rpx, height: 40.rpx))
  210. make.right.equalTo(-8.rpx)
  211. make.bottom.equalTo(friBgView.snp.top).offset(-12.rpx)
  212. }
  213. friHeaderView.snp.makeConstraints { make in
  214. make.left.right.equalTo(0)
  215. make.height.equalTo(61.rpx)
  216. make.top.equalTo(0)
  217. }
  218. friHeaderTitleIcon.snp.makeConstraints { make in
  219. make.size.equalTo(CGSize(width: 82.rpx, height: 26.5.rpx))
  220. make.left.equalTo(16.rpx)
  221. make.top.equalTo(16.rpx)
  222. }
  223. friHeaderAddBtn.snp.makeConstraints { make in
  224. make.size.equalTo(CGSize(width: 106.rpx, height: 32.rpx))
  225. make.right.equalTo(-8.rpx)
  226. make.centerY.equalTo(friHeaderTitleIcon.snp.centerY)
  227. }
  228. friTableView.snp.makeConstraints { make in
  229. make.left.equalTo(0)
  230. make.right.equalTo(0)
  231. make.top.equalTo(friBgView.snp.top).offset(50.rpx)
  232. make.bottom.equalTo(-12.rpx)
  233. }
  234. }
  235. }
  236. // MARK: - 点击事件
  237. extension QSLHomeFriendView {
  238. @objc func unlockBtnAction(){
  239. gravityInstance?.track(QSLGravityConst.home_coupon_click, properties: [:])
  240. QSLJumpManager.shared.unlockBtnAction()
  241. }
  242. @objc func homeFriPhoneViewAction() {
  243. delegate?.homeFriPhoneViewClick()
  244. }
  245. @objc func topAddButtonAction() {
  246. delegate?.addFriendAction(isSmall: true)
  247. }
  248. }
  249. extension QSLHomeFriendView: QSLHomeFriendTableViewCellDelegate {
  250. func routeBtnAction(model: QSLUserModel) {
  251. delegate?.routeBtnAction(model: model)
  252. }
  253. func locateBtnAction(model: QSLUserModel) {
  254. delegate?.locateBtnAction(model: model)
  255. }
  256. @objc private func handleCouponNotification(_ notification: Notification) {
  257. guard let shouldShow = notification.userInfo?["showCoupon"] as? Bool else { return }
  258. updateCouponViewVisibility(show: shouldShow)
  259. }
  260. private func updateCouponViewVisibility(show: Bool) {
  261. if(couponView.isHidden == !show){
  262. return
  263. }
  264. couponView.isHidden = !show
  265. if show {
  266. if let model = QSLCountdownManager.shared.selectGood {
  267. let amount : Int = Int((model.originalAmount - model.amount) / 100)
  268. let attr = NSMutableAttributedString()
  269. let firstText = "您有一订单未支付专属优惠券 "
  270. let firstAttr = NSMutableAttributedString(string: firstText)
  271. firstAttr.yy_font = UIFont.textM(10)
  272. firstAttr.yy_color = UIColor.white
  273. attr.append(firstAttr)
  274. if amount > 0 {
  275. let secondText = "-¥\(amount)"
  276. let secondAttr = NSMutableAttributedString(string: secondText)
  277. secondAttr.yy_font = UIFont.textB(15)
  278. secondAttr.yy_color = UIColor.hexStringColor(hexString: "#E1E3A3")
  279. attr.append(secondAttr)
  280. }
  281. self.couponLabel.attributedText = attr
  282. QSLCountdownManager.shared.updateHandler = { [weak self] timeString in
  283. self?.countdownLabel.updateCountdownText(timeString)
  284. }
  285. QSLCountdownManager.shared.finishHandler = { [weak self] in
  286. self?.updateCouponViewVisibility(show: false)
  287. }
  288. QSLCountdownManager.shared.startCountdown()
  289. }
  290. couponViewTopConstraint?.update(offset: 8.rpx)
  291. friBgViewTopConstraint?.update(offset: 8.rpx) // 修改这里
  292. friBgView.snp.remakeConstraints { make in
  293. make.top.equalTo(couponView.snp.bottom).offset(0)
  294. make.left.bottom.right.equalToSuperview()
  295. }
  296. refreshButton.snp.remakeConstraints { make in
  297. make.size.equalTo(CGSize(width: 40.rpx, height: 40.rpx))
  298. make.right.equalTo(-8.rpx)
  299. make.bottom.equalTo(couponView.snp.top).offset(-12.rpx)
  300. }
  301. } else {
  302. couponViewTopConstraint?.update(offset: 0)
  303. friBgView.snp.remakeConstraints { make in
  304. make.top.equalTo(friExpandButton.snp.bottom).offset(8.rpx)
  305. make.left.bottom.right.equalToSuperview()
  306. }
  307. refreshButton.snp.remakeConstraints { make in
  308. make.size.equalTo(CGSize(width: 40.rpx, height: 40.rpx))
  309. make.right.equalTo(-8.rpx)
  310. make.bottom.equalTo(friBgView.snp.top).offset(-12.rpx)
  311. }
  312. }
  313. UIView.animate(withDuration: 0.3) {
  314. self.layoutIfNeeded()
  315. }
  316. }
  317. }
  318. // MARK: - 添加滑动逻辑
  319. extension QSLHomeFriendView: UIGestureRecognizerDelegate {
  320. func addPanAction() {
  321. let pan = UIPanGestureRecognizer(target: self, action: #selector(panAction))
  322. pan.delegate = self
  323. self.addGestureRecognizer(pan)
  324. }
  325. @objc func panAction(pan: UIPanGestureRecognizer) {
  326. let point = pan.translation(in: self)
  327. if let scrollStopY = self.scrollStopY, scrollStopY > 0 {
  328. pan.setTranslation(CGPoint(x: 0, y: 0), in: self)
  329. return
  330. }
  331. self.qsl_top += point.y
  332. if let scrollTopHeight = self.scrollTopHeight, self.qsl_top < scrollTopHeight {
  333. self.qsl_top = scrollTopHeight
  334. }
  335. if let scrollBottomHeight = self.scrollBottomHeight, self.qsl_top > scrollBottomHeight {
  336. self.qsl_top = scrollBottomHeight
  337. }
  338. if pan.state == .ended || pan.state == .cancelled {
  339. let velocity = pan.velocity(in: self)
  340. let speed = 350.0
  341. if let scrollLocation = self.scrollLocation {
  342. switch scrollLocation {
  343. case .ScrollTop:
  344. if velocity.y < -speed {
  345. self.scrollToLocation(type: .ScrollTop)
  346. pan.setTranslation(CGPoint(x: 0, y: 0), in: self)
  347. return
  348. } else if velocity.y > speed {
  349. self.scrollToLocation(type: .ScrollCenter)
  350. pan.setTranslation(CGPoint(x: 0, y: 0), in: self)
  351. return
  352. }
  353. break
  354. case .ScrollCenter:
  355. if velocity.y < -speed {
  356. self.scrollToLocation(type: .ScrollTop)
  357. pan.setTranslation(CGPoint(x: 0, y: 0), in: self)
  358. return
  359. } else if velocity.y > speed {
  360. self.scrollToLocation(type: .ScrollBottom)
  361. pan.setTranslation(CGPoint(x: 0, y: 0), in: self)
  362. return
  363. }
  364. break
  365. case .ScrollBottom:
  366. if velocity.y < -speed {
  367. self.scrollToLocation(type: .ScrollCenter)
  368. pan.setTranslation(CGPoint(x: 0, y: 0), in: self)
  369. return
  370. } else if velocity.y > speed {
  371. self.scrollToLocation(type: .ScrollBottom)
  372. pan.setTranslation(CGPoint(x: 0, y: 0), in: self)
  373. return
  374. }
  375. break
  376. }
  377. }
  378. if self.qsl_top < (qsl_kScreenH - qsl_kTabbarBottom) / 4 {
  379. self.scrollToLocation(type: .ScrollTop)
  380. } else if self.qsl_top < (qsl_kScreenH - qsl_kTabbarBottom) / 4 * 3 && self.qsl_top > (qsl_kScreenH - qsl_kTabbarBottom) / 4 {
  381. self.scrollToLocation(type: .ScrollCenter)
  382. } else {
  383. self.scrollToLocation(type: .ScrollBottom)
  384. }
  385. }
  386. pan.setTranslation(CGPoint(x: 0, y: 0), in: self)
  387. }
  388. func scrollToLocation(type: FriendViewLocation) {
  389. let animation = CASpringAnimation(keyPath: "position.y")
  390. animation.damping = 10
  391. animation.stiffness = 200
  392. animation.mass = 1
  393. animation.initialVelocity = 10
  394. animation.duration = animation.settlingDuration
  395. // animation.fromValue = self.friPhoneView.layer.position.y
  396. // animation.toValue = self.friPhoneView.layer.position.y + 2
  397. animation.isRemovedOnCompletion = false
  398. animation.fillMode = .forwards
  399. let animation1 = CASpringAnimation(keyPath: "position.y")
  400. animation1.damping = 10
  401. animation1.stiffness = 200
  402. animation1.mass = 1
  403. animation1.initialVelocity = 10
  404. animation1.duration = animation.settlingDuration
  405. animation1.fromValue = self.friTableView.layer.position.y
  406. animation1.toValue = self.friTableView.layer.position.y + 2
  407. animation1.isRemovedOnCompletion = false
  408. animation1.fillMode = .forwards
  409. self.scrollLocation = type
  410. if let topHeight = self.scrollTopHeight, let centerHeight = self.scrollCenterHeight, let bottomHeight = self.scrollBottomHeight {
  411. switch type {
  412. case .ScrollTop:
  413. UIView.animate(withDuration: 0.2) {
  414. self.qsl_top = topHeight
  415. } completion: { finished in
  416. self.friTableView.isScrollEnabled = true
  417. // self.friPhoneView.layer.add(animation, forKey: "animation")
  418. self.friTableView.layer.add(animation1, forKey: "animation")
  419. }
  420. break
  421. case .ScrollCenter:
  422. UIView.animate(withDuration: 0.2) {
  423. self.qsl_top = centerHeight
  424. } completion: { finished in
  425. self.friTableView.isScrollEnabled = false
  426. // self.friPhoneView.layer.add(animation, forKey: "animation")
  427. self.friTableView.layer.add(animation1, forKey: "animation")
  428. }
  429. break
  430. case .ScrollBottom:
  431. UIView.animate(withDuration: 0.2) {
  432. self.qsl_top = bottomHeight
  433. } completion: { finished in
  434. self.friTableView.isScrollEnabled = false
  435. // self.friPhoneView.layer.add(animation, forKey: "animation")
  436. self.friTableView.layer.add(animation1, forKey: "animation")
  437. }
  438. break
  439. }
  440. }
  441. delegate?.viewDidScroll()
  442. }
  443. func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
  444. return true
  445. }
  446. func scrollViewDidScroll(_ scrollView: UIScrollView) {
  447. let currentPostion = scrollView.contentOffset.y
  448. self.scrollStopY = currentPostion
  449. }
  450. }
  451. extension QSLHomeFriendView: QSLHomeFriendFooterViewDelegate {
  452. func addButtonAction() {
  453. delegate?.addFriendAction(isSmall: false)
  454. }
  455. }
  456. // MARK: - 设置Tableview
  457. extension QSLHomeFriendView: UITableViewDelegate, UITableViewDataSource {
  458. func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  459. guard let friendList = viewModel?.friendList else { return 0 }
  460. return friendList.count
  461. }
  462. func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
  463. // if indexPath.row == 0 {
  464. //
  465. // cell.addCorner(conrners: [.topRight, .topLeft], radius: 6)
  466. // }
  467. //
  468. // if let friendList = viewModel?.friendList, indexPath.row == friendList.count - 1 {
  469. //
  470. // cell.addCorner(conrners: [.bottomLeft, .bottomRight], radius: 6)
  471. // }
  472. }
  473. func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  474. let cell = tableView.dequeueReusableCell(cellType: QSLHomeFriendTableViewCell.self, cellForRowAt: indexPath)
  475. cell.selectionStyle = .none
  476. cell.delegate = self
  477. if let model = viewModel?.friendList[indexPath.row] {
  478. cell.config(model: model)
  479. }
  480. return cell
  481. }
  482. func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
  483. return 94.rpx
  484. }
  485. func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
  486. return 0.0001
  487. }
  488. func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
  489. return 200.rpx
  490. }
  491. func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
  492. let view = QSLHomeFriendFooterView()
  493. view.delegate = self
  494. return view
  495. }
  496. }