// // QSLHomeFriendView.swift // QuickSearchLocation // // Created by mac on 2024/4/12. // import UIKit import YYText import SnapKit enum FriendViewLocation { case ScrollTop case ScrollCenter case ScrollBottom } protocol QSLHomeFriendViewDelegate: NSObjectProtocol { func refreshBtnAction() func homeFriPhoneViewClick() func routeBtnAction(model: QSLUserModel) func locateBtnAction(model: QSLUserModel) func addFriendAction(isSmall: Bool) func viewDidScroll() } class QSLHomeFriendView: UIView { weak var delegate: QSLHomeFriendViewDelegate? // 动态约束处理 var couponViewTopConstraint: Constraint? = nil private var friBgViewTopConstraint: Constraint? // 新增:保存friBgView的顶部约束 var viewModel:QSLHomeViewModel? { didSet { friTableView.reloadData() } } /// 位置 var scrollLocation: FriendViewLocation? /// 滑动到最后停下来的位置 var scrollStopY: CGFloat? /// 滑动顶部距离顶部的距离 var scrollTopHeight: CGFloat? /// 滑动中间距离顶部的距离 var scrollCenterHeight: CGFloat? /// 滑动底部距离顶部的距离 var scrollBottomHeight: CGFloat? lazy var friMapLogoImageView: UIImageView = { let imageView = UIImageView() imageView.isHidden = true imageView.image = UIImage(named: "home_friends_map_logo") return imageView }() lazy var friExpandButton: UIButton = { let button = UIButton(type: .custom) button.backgroundColor = UIColor.hexStringColor(hexString: "#333333", alpha: 0.6) button.addRadius(radius: 2) return button }() lazy var refreshButton: UIButton = { let button = UIButton(type: .custom) button.setBackgroundImage(UIImage(named: "home_refresh_btn"), for: .normal) button.addTarget(self, action: #selector(refreshBtnAction), for: .touchUpInside) return button }() lazy var couponView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "home_activity_bg") view.isUserInteractionEnabled = true view.isHidden = true let tapG = UITapGestureRecognizer.init(target: self, action: #selector(unlockBtnAction)) view.addGestureRecognizer(tapG) return view }() lazy var couponLabel: YYLabel = { let label = YYLabel() return label }() lazy var countdownLabel: QSLCountdownView = { let label = QSLCountdownView(frame: CGRectMake(0, 0, QSLConst.qsl_kScreenW - 24.rpx, 17.rpx),type: 1) return label }() lazy var couponBtn: UILabel = { let label = UILabel() label.gradientBackgroundColor(color1: UIColor.hexStringColor(hexString: "#FFFEF3", alpha: 1), color2: UIColor.hexStringColor(hexString: "#FFE6C0", alpha: 1), width: 65.rpx, height: 28.rpx, direction: .horizontal) label.text("去使用") label.textAlignment = .center label.font = UIFont.textM(14) label.textColor = (UIColor.hexStringColor(hexString: "#3A331C")) label.addRadius(radius: 14.rpx) return label }() lazy var friBgView: UIView = { let view = UIView() view.clipsToBounds = true view.backgroundColor = QSLColor.backGroundColor view.addRadius(radius: 12.rpx) return view }() lazy var friHeaderView: UIView = { let view = UIView(frame: CGRect(x: 0, y: 0, width: QSLConst.qsl_kScreenW, height: 61.rpx)) 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) { view.backgroundColor = UIColor(patternImage: image) } view.addFourCorner(topLeft: 12.rpx, topRight: 12.rpx, bottomLeft: 0, bottomRight: 0) return view }() lazy var friHeaderTitleIcon: UIImageView = { let imageView = UIImageView() imageView.image = UIImage(named: "home_friends_header_title") return imageView }() lazy var friHeaderAddBtn: UIButton = { let btn = UIButton() btn.setBackgroundImage(UIImage(named: "home_friends_header_add_btn_bg"), for: .normal) btn.image(UIImage(named: "home_friends_header_add_icon")) btn.title("查找好友") btn.mediumFont(15) btn.textColor(.white) btn.setImageTitleLayout(.imgLeft, spacing: 0) btn.addTarget(self, action: #selector(topAddButtonAction), for: .touchUpInside) return btn }() lazy var friTableView: UITableView = { let tableView = UITableView(frame: .zero, style: .grouped) tableView.backgroundColor = .clear tableView.separatorStyle = .none tableView.showsVerticalScrollIndicator = false tableView.delegate = self tableView.dataSource = self tableView.tableViewNeverAdjustContentInset() tableView.bounces = false tableView.isUserInteractionEnabled = true tableView.isScrollEnabled = false tableView.contentInsetAdjustmentBehavior = .never tableView.register(cellClass: QSLHomeFriendTableViewCell.self) return tableView }() override init(frame: CGRect) { super.init(frame: frame) self.scrollCenterHeight = self.qsl_top self.scrollBottomHeight = qsl_kScreenH - qsl_kTabbarFrameH - 56 - 40 self.setupUI() self.addPanAction() NotificationCenter.default.addObserver( self, selector: #selector(handleCouponNotification(_:)), name: Notification.Name("QSLHomeUpdateCouponViewNoti"), object: nil ) //更新首页优惠券价格 NotificationCenter.default.addObserver( self, selector: #selector(refreshCouponNotification(_:)), name: Notification.Name("QSLHomeRefreshCouponViewNoti"), object: nil ) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @objc func refreshBtnAction() { delegate?.refreshBtnAction() } } // MARK: - 设置UI extension QSLHomeFriendView { func setupUI() { addSubview(couponView) couponView.addSubview(couponBtn) couponView.addSubview(couponLabel) couponView.addSubview(countdownLabel) addSubview(friBgView) addSubview(friTableView) addSubview(friMapLogoImageView) addSubview(friExpandButton) addSubview(refreshButton) friBgView.addSubview(friHeaderView) friHeaderView.addSubview(friHeaderTitleIcon) friHeaderView.addSubview(friHeaderAddBtn) friMapLogoImageView.snp.makeConstraints { make in make.size.equalTo(CGSize(width: 62, height: 20)) make.top.equalTo(0) make.left.equalTo(12) } friExpandButton.snp.makeConstraints { make in make.size.equalTo(CGSize(width: 80, height: 4)) make.top.equalTo(friMapLogoImageView.snp.bottom).offset(8) make.centerX.equalTo(snp.centerX) } // couponView 的约束(初始时设为隐藏) couponView.snp.makeConstraints { make in make.left.equalTo(8.rpx) make.right.equalTo(-8.rpx) make.height.equalTo(58.rpx) couponViewTopConstraint = make.top.equalTo(friExpandButton.snp.bottom).offset(8.rpx).constraint } couponLabel.snp.makeConstraints { make in make.height.equalTo(22.rpx) make.top.equalTo(10.rpx) make.left.equalTo(60.rpx) make.right.equalTo(-12.rpx) } couponBtn.snp.makeConstraints { make in make.height.equalTo(28.rpx) make.width.equalTo(65.rpx) make.right.equalTo(-12.rpx) make.centerY.equalToSuperview() } countdownLabel.snp.makeConstraints { make in make.height.equalTo(17.rpx) make.top.equalTo(couponLabel.snp.bottom).offset(0) make.left.equalTo(60.rpx) make.right.equalTo(couponBtn.snp.left).offset(-10.rpx) } friBgView.snp.makeConstraints { make in friBgViewTopConstraint = make.top.equalTo(friExpandButton.snp.bottom).offset(8.rpx).constraint make.left.bottom.right.equalToSuperview() } refreshButton.snp.makeConstraints { make in make.size.equalTo(CGSize(width: 40.rpx, height: 40.rpx)) make.right.equalTo(-8.rpx) make.bottom.equalTo(friBgView.snp.top).offset(-12.rpx) } friHeaderView.snp.makeConstraints { make in make.left.right.equalTo(0) make.height.equalTo(61.rpx) make.top.equalTo(0) } friHeaderTitleIcon.snp.makeConstraints { make in make.size.equalTo(CGSize(width: 82.rpx, height: 26.5.rpx)) make.left.equalTo(16.rpx) make.top.equalTo(16.rpx) } friHeaderAddBtn.snp.makeConstraints { make in make.size.equalTo(CGSize(width: 106.rpx, height: 32.rpx)) make.right.equalTo(-8.rpx) make.centerY.equalTo(friHeaderTitleIcon.snp.centerY) } friTableView.snp.makeConstraints { make in make.left.equalTo(0) make.right.equalTo(0) make.top.equalTo(friBgView.snp.top).offset(50.rpx) make.bottom.equalTo(-12.rpx) } } } // MARK: - 点击事件 extension QSLHomeFriendView { @objc func unlockBtnAction(){ QSEventHandle.eventPush(eventName: QSLGravityConst.home_coupon_click) QSLJumpManager.shared.unlockBtnAction() } @objc func homeFriPhoneViewAction() { delegate?.homeFriPhoneViewClick() } @objc func topAddButtonAction() { delegate?.addFriendAction(isSmall: true) } } extension QSLHomeFriendView: QSLHomeFriendTableViewCellDelegate { func routeBtnAction(model: QSLUserModel) { delegate?.routeBtnAction(model: model) } func locateBtnAction(model: QSLUserModel) { delegate?.locateBtnAction(model: model) } @objc private func handleCouponNotification(_ notification: Notification) { guard let shouldShow = notification.userInfo?["showCoupon"] as? Bool else { return } updateCouponViewVisibility(show: shouldShow) } @objc private func refreshCouponNotification(_ notification: Notification) { refreshCouponView() } func refreshCouponView(){ if let model = QSLCountdownManager.shared.selectGood { let amount : Int = Int((model.originalAmount - model.amount) / 100) let attr = NSMutableAttributedString() let firstText = "您有一订单未支付专属优惠券 " let firstAttr = NSMutableAttributedString(string: firstText) firstAttr.yy_font = UIFont.textM(10) firstAttr.yy_color = UIColor.white attr.append(firstAttr) if amount > 0 { let secondText = "-¥\(amount)" let secondAttr = NSMutableAttributedString(string: secondText) secondAttr.yy_font = UIFont.textB(18) secondAttr.yy_color = UIColor.hexStringColor(hexString: "#E1E3A3") attr.append(secondAttr) } self.couponLabel.attributedText = attr if(self.countdownLabel.label.text != nil){ return } QSLCountdownManager.shared.updateHandler = { [weak self] timeString in if let curVC = self?.rootViewController(){ if(curVC.isKind(of: QSLHomeController.self)){ self?.countdownLabel.updateCountdownText(timeString) } } } QSLCountdownManager.shared.finishHandler = { [weak self] in self?.updateCouponViewVisibility(show: false) } QSLCountdownManager.shared.startCountdown() } } private func updateCouponViewVisibility(show: Bool) { if(couponView.isHidden == !show){ self.refreshCouponView() return } couponView.isHidden = !show if (show) { self.refreshCouponView() couponViewTopConstraint?.update(offset: 8.rpx) friBgViewTopConstraint?.update(offset: 8.rpx) // 修改这里 friBgView.snp.remakeConstraints { make in make.top.equalTo(couponView.snp.bottom).offset(0) make.left.bottom.right.equalToSuperview() } refreshButton.snp.remakeConstraints { make in make.size.equalTo(CGSize(width: 40.rpx, height: 40.rpx)) make.right.equalTo(-8.rpx) make.bottom.equalTo(couponView.snp.top).offset(-12.rpx) } } else { couponViewTopConstraint?.update(offset: 0) friBgView.snp.remakeConstraints { make in make.top.equalTo(friExpandButton.snp.bottom).offset(8.rpx) make.left.bottom.right.equalToSuperview() } refreshButton.snp.remakeConstraints { make in make.size.equalTo(CGSize(width: 40.rpx, height: 40.rpx)) make.right.equalTo(-8.rpx) make.bottom.equalTo(friBgView.snp.top).offset(-12.rpx) } } UIView.animate(withDuration: 0.3) { self.layoutIfNeeded() } } } // MARK: - 添加滑动逻辑 extension QSLHomeFriendView: UIGestureRecognizerDelegate { func addPanAction() { let pan = UIPanGestureRecognizer(target: self, action: #selector(panAction)) pan.delegate = self self.addGestureRecognizer(pan) } @objc func panAction(pan: UIPanGestureRecognizer) { let point = pan.translation(in: self) if let scrollStopY = self.scrollStopY, scrollStopY > 0 { pan.setTranslation(CGPoint(x: 0, y: 0), in: self) return } self.qsl_top += point.y if let scrollTopHeight = self.scrollTopHeight, self.qsl_top < scrollTopHeight { self.qsl_top = scrollTopHeight } if let scrollBottomHeight = self.scrollBottomHeight, self.qsl_top > scrollBottomHeight { self.qsl_top = scrollBottomHeight } if pan.state == .ended || pan.state == .cancelled { let velocity = pan.velocity(in: self) let speed = 350.0 if let scrollLocation = self.scrollLocation { switch scrollLocation { case .ScrollTop: if velocity.y < -speed { self.scrollToLocation(type: .ScrollTop) pan.setTranslation(CGPoint(x: 0, y: 0), in: self) return } else if velocity.y > speed { self.scrollToLocation(type: .ScrollCenter) pan.setTranslation(CGPoint(x: 0, y: 0), in: self) return } break case .ScrollCenter: if velocity.y < -speed { self.scrollToLocation(type: .ScrollTop) pan.setTranslation(CGPoint(x: 0, y: 0), in: self) return } else if velocity.y > speed { self.scrollToLocation(type: .ScrollBottom) pan.setTranslation(CGPoint(x: 0, y: 0), in: self) return } break case .ScrollBottom: if velocity.y < -speed { self.scrollToLocation(type: .ScrollCenter) pan.setTranslation(CGPoint(x: 0, y: 0), in: self) return } else if velocity.y > speed { self.scrollToLocation(type: .ScrollBottom) pan.setTranslation(CGPoint(x: 0, y: 0), in: self) return } break } } if self.qsl_top < (qsl_kScreenH - qsl_kTabbarBottom) / 4 { self.scrollToLocation(type: .ScrollTop) } else if self.qsl_top < (qsl_kScreenH - qsl_kTabbarBottom) / 4 * 3 && self.qsl_top > (qsl_kScreenH - qsl_kTabbarBottom) / 4 { self.scrollToLocation(type: .ScrollCenter) } else { self.scrollToLocation(type: .ScrollBottom) } } pan.setTranslation(CGPoint(x: 0, y: 0), in: self) } func scrollToLocation(type: FriendViewLocation) { let animation = CASpringAnimation(keyPath: "position.y") animation.damping = 10 animation.stiffness = 200 animation.mass = 1 animation.initialVelocity = 10 animation.duration = animation.settlingDuration // animation.fromValue = self.friPhoneView.layer.position.y // animation.toValue = self.friPhoneView.layer.position.y + 2 animation.isRemovedOnCompletion = false animation.fillMode = .forwards let animation1 = CASpringAnimation(keyPath: "position.y") animation1.damping = 10 animation1.stiffness = 200 animation1.mass = 1 animation1.initialVelocity = 10 animation1.duration = animation.settlingDuration animation1.fromValue = self.friTableView.layer.position.y animation1.toValue = self.friTableView.layer.position.y + 2 animation1.isRemovedOnCompletion = false animation1.fillMode = .forwards self.scrollLocation = type if let topHeight = self.scrollTopHeight, let centerHeight = self.scrollCenterHeight, let bottomHeight = self.scrollBottomHeight { switch type { case .ScrollTop: UIView.animate(withDuration: 0.2) { self.qsl_top = topHeight } completion: { finished in self.friTableView.isScrollEnabled = true // self.friPhoneView.layer.add(animation, forKey: "animation") self.friTableView.layer.add(animation1, forKey: "animation") } break case .ScrollCenter: UIView.animate(withDuration: 0.2) { self.qsl_top = centerHeight } completion: { finished in self.friTableView.isScrollEnabled = false // self.friPhoneView.layer.add(animation, forKey: "animation") self.friTableView.layer.add(animation1, forKey: "animation") } break case .ScrollBottom: UIView.animate(withDuration: 0.2) { self.qsl_top = bottomHeight } completion: { finished in self.friTableView.isScrollEnabled = false // self.friPhoneView.layer.add(animation, forKey: "animation") self.friTableView.layer.add(animation1, forKey: "animation") } break } } delegate?.viewDidScroll() } func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } func scrollViewDidScroll(_ scrollView: UIScrollView) { let currentPostion = scrollView.contentOffset.y self.scrollStopY = currentPostion } } extension QSLHomeFriendView: QSLHomeFriendFooterViewDelegate { func addButtonAction() { delegate?.addFriendAction(isSmall: false) } } // MARK: - 设置Tableview extension QSLHomeFriendView: UITableViewDelegate, UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { guard let friendList = viewModel?.friendList else { return 0 } return friendList.count } func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { // if indexPath.row == 0 { // // cell.addCorner(conrners: [.topRight, .topLeft], radius: 6) // } // // if let friendList = viewModel?.friendList, indexPath.row == friendList.count - 1 { // // cell.addCorner(conrners: [.bottomLeft, .bottomRight], radius: 6) // } } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(cellType: QSLHomeFriendTableViewCell.self, cellForRowAt: indexPath) cell.selectionStyle = .none cell.delegate = self if let model = viewModel?.friendList[indexPath.row] { cell.config(model: model) } return cell } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 94.rpx } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 0.0001 } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 200.rpx } func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { let view = QSLHomeFriendFooterView() view.delegate = self return view } }