jsdw_ios/QuickLocation/Section/Group/GroupView.swift

655 lines
20 KiB
Swift
Raw Permalink 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.

//
// GroupView.swift
// QuickLocation
//
// Created by on 2026/5/29.
//
import UIKit
import RxSwift
import RxCocoa
import SDCycleScrollView
import OpenIMSDK
// MARK: - PanScrollView PanScrollView
class PanScrollView: UIScrollView {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
class GroupView: UIView {
var disposeBag = DisposeBag()
///
private var isSticky = false
/// tableview
private var isSubCanScroll = false
var stickThreshold: CGFloat {
return segmentView.dl.y
}
// MARK: - Setup
private func setupUI() {
addSubview(navBgView)
addSubview(navBarView)
navBarView.addSubview(navTitleLabel)
navBarView.addSubview(scanBtn)
addSubview(mainScrollView)
mainScrollView.addSubview(contentView)
contentView.addSubview(cycleScrollView)
contentView.addSubview(actionButtonsView)
actionButtonsView.addSubview(createGroupBtn)
actionButtonsView.addSubview(joinGroupBtn)
contentView.addSubview(hotGroupTitleLabel)
contentView.addSubview(hotGroupsCollectionView)
contentView.addSubview(segmentView)
segmentView.addSubview(createdTabLabel)
segmentView.addSubview(joinedTabLabel)
segmentView.addSubview(tabIndicator)
contentView.addSubview(segmentScrollView)
segmentScrollView.addSubview(segmentContentView)
segmentContentView.addSubview(createdTableView)
segmentContentView.addSubview(joinedTableView)
navBgView.layoutChain
.edges(excludingEdge: .bottom)
.height(kNaviHeight)
navBarView.layoutChain
.edges(excludingEdge: .bottom)
.height(kNaviHeight)
navTitleLabel.layoutChain
.top(kStatusBarHeight + 12)
.centerX()
scanBtn.layoutChain
.right(15)
.centerY(navTitleLabel)
.width(24).height(24)
mainScrollView.layoutChain
.topToBottomOfView(navBgView)
.edges(excludingEdge: .top)
contentView.layoutChain
.edges()
.widthToView(mainScrollView)
//
cycleScrollView.layoutChain
.top(10)
.left(15).right(15)
.heightToWidth(100/375)
// /
actionButtonsView.layoutChain
.topToBottomOfView(cycleScrollView, offset: 38)
.edgesHorzontal()
createGroupBtn.layoutChain
.left(15)
.edgesVertical()
.widthToView(joinGroupBtn)
.height(40)
joinGroupBtn.layoutChain
.topToView(createGroupBtn)
.bottomToView(createGroupBtn)
.leftToRightOfView(createGroupBtn, offset: 21)
.right(15)
.widthToView(joinGroupBtn)
//
hotGroupTitleLabel.layoutChain
.topToBottomOfView(actionButtonsView, offset: 21)
.left(16)
hotGroupsCollectionView.layoutChain
.topToBottomOfView(hotGroupTitleLabel, offset: 8)
.edgesHorzontal()
.height(88)
// Segment
let labelWidth = (UIScreen.main.bounds.width - 60) / 2
segmentView.layoutChain
.topToBottomOfView(hotGroupsCollectionView)
.edgesHorzontal()
.height(44)
createdTabLabel.layoutChain
.left(30).centerY()
.width(labelWidth)
joinedTabLabel.layoutChain
.right(30).centerY()
.width(labelWidth)
tabIndicator.layoutChain
.bottom().centerX(createdTabLabel)
.width(14).height(3)
// segmentScrollView
segmentScrollView.layoutChain
.topToBottomOfView(segmentView)
.edgesHorzontal()
.bottom()
.height(kScreenHeight - kNaviHeight - 44)
segmentContentView.layoutChain
.edges()
.heightToView(segmentScrollView)
let svWidth = UIScreen.main.bounds.width
createdTableView.layoutChain
.top().bottom().left()
.width(svWidth)
joinedTableView.layoutChain
.top().bottom()
.leftToRightOfView(createdTableView)
.width(svWidth)
}
override func layoutSubviews() {
super.layoutSubviews()
segmentContentView.layoutChain.width(bounds.width * 2)
}
// MARK: - Nav
lazy var navBgView: UIImageView = {
let iv = UIImageView()
iv.image = UIImage(named: "Common/navBar_bg_1")
iv.contentMode = .scaleAspectFill
return iv
}()
lazy var navBarView: UIView = {
let view = UIView()
view.backgroundColor = .clear
return view
}()
lazy var navTitleLabel: UILabel = {
let label = UILabel()
label.text = "圈子"
label.font = .systemFont(ofSize: 18, weight: .medium)
label.textColor = ThemeManager.shared.color.titleAuxColor
label.textAlignment = .center
return label
}()
lazy var scanBtn: UIButton = {
let btn = UIButton(type: .custom)
btn.setImage(UIImage(named: "Group/scan"), for: .normal)
return btn
}()
// MARK: - Main Scroll
lazy var mainScrollView: PanScrollView = {
let sv = PanScrollView()
sv.backgroundColor = .clear
sv.delegate = self
sv.showsVerticalScrollIndicator = false
sv.bounces = false
return sv
}()
lazy var contentView: UIView = {
let v = UIView()
v.backgroundColor = .clear
return v
}()
// MARK: -
lazy var cycleScrollView: SDCycleScrollView = {
let view = SDCycleScrollView(frame: .zero)
view.backgroundColor = .lightGray
view.scrollDirection = .horizontal
view.showPageControl = true
view.bannerImageViewContentMode = .scaleAspectFill
view.autoScrollTimeInterval = 5
view.currentPageDotColor = UIColor(hexStr: "#16B3FF")
view.pageDotColor = UIColor(hexStr: "#7AD6FF").withAlphaComponent(0.4)
view.pageControlDotSize = CGSize(width: 8, height: 8)
view.clipsToBounds = false
view.pageControlBottomOffset = -34
return view
}()
// MARK: - /
private lazy var actionButtonsView: UIView = {
let v = UIView()
v.backgroundColor = .clear
return v
}()
lazy var createGroupBtn: UIButton = {
let btn = UIButton(type: .custom)
btn.setTitle("创建圈子", for: .normal)
btn.setTitleColor(UIColor(hexStr: "#0F2846"), for: .normal)
btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .bold)
btn.setBackgroundImage(UIImage(named: "Common/gradient_bg"), for: .normal)
btn.cornerRadius = 20
return btn
}()
lazy var joinGroupBtn: UIButton = {
let btn = UIButton(type: .custom)
btn.setTitle("加入圈子", for: .normal)
btn.setTitleColor(UIColor(hexStr: "#0F2846"), for: .normal)
btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .bold)
btn.setBackgroundImage(UIImage(named: "Common/gradient_bg"), for: .normal)
btn.cornerRadius = 20
return btn
}()
// MARK: -
private lazy var hotGroupTitleLabel: UILabel = {
let label = UILabel()
label.text = "热门酷圈"
label.font = .systemFont(ofSize: 16, weight: .semibold)
label.textColor = ThemeManager.shared.color.titleAuxColor
return label
}()
private(set) lazy var hotGroupsCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.itemSize = CGSize(width: 75, height: 88)
layout.minimumLineSpacing = 10
layout.sectionInset = UIEdgeInsets(top: 0, left: 18, bottom: 0, right: 18)
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .clear
cv.showsHorizontalScrollIndicator = false
cv.register(HotGroupCell.self, forCellWithReuseIdentifier: HotGroupCell.reuseId)
return cv
}()
// MARK: - Segment
private(set) lazy var segmentView: UIView = {
let v = UIView()
v.backgroundColor = .white
let line = UIView()
line.backgroundColor = UIColor(hexStr: "#B5B5B5").withAlphaComponent(0.12)
v.addSubview(line)
line.layoutChain
.edgesHorzontal()
.height(1)
.bottom(4)
return v
}()
private(set) lazy var createdTabLabel: UILabel = {
let label = UILabel()
label.text = "我创建的圈子"
label.font = .systemFont(ofSize: 15, weight: .semibold)
label.textColor = UIColor(hexStr: "#1A1A1A")
label.textAlignment = .center
label.isUserInteractionEnabled = true
return label
}()
private(set) lazy var joinedTabLabel: UILabel = {
let label = UILabel()
label.text = "我加入的圈子"
label.font = .systemFont(ofSize: 15, weight: .medium)
label.textColor = UIColor(hexStr: "#999999")
label.textAlignment = .center
label.isUserInteractionEnabled = true
return label
}()
private lazy var tabIndicator: UIView = {
let v = UIView()
v.backgroundColor = UIColor(hexStr: "#16B3FF")
v.layer.cornerRadius = 2
return v
}()
// MARK: - (PanScrollView)
private(set) lazy var segmentScrollView: PanScrollView = {
let sv = PanScrollView()
sv.isPagingEnabled = true
sv.showsHorizontalScrollIndicator = false
sv.bounces = false
sv.delegate = self
return sv
}()
private lazy var segmentContentView: UIView = {
let v = UIView()
v.backgroundColor = .clear
return v
}()
// MARK: -
private(set) lazy var createdTableView: UITableView = {
let tv = UITableView(frame: .zero, style: .plain)
tv.backgroundColor = .clear
tv.separatorStyle = .none
tv.showsVerticalScrollIndicator = false
tv.register(CircleGroupCell.self)
tv.estimatedRowHeight = 70
tv.bounces = true
tv.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 97 + kSafeBottomMargin, right: 0)
return tv
}()
private(set) lazy var joinedTableView: UITableView = {
let tv = UITableView(frame: .zero, style: .plain)
tv.backgroundColor = .clear
tv.separatorStyle = .none
tv.showsVerticalScrollIndicator = false
tv.register(CircleGroupCell.self)
tv.estimatedRowHeight = 70
tv.bounces = true
tv.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 97 + kSafeBottomMargin, right: 0)
return tv
}()
// MARK: - Segment
var currentSegmentIndex: Int = 0
func selectSegment(at index: Int) {
currentSegmentIndex = index
let isCreated = index == 0
UIView.animate(withDuration: 0.2) {
self.createdTabLabel.textColor = isCreated ? UIColor(hexStr: "#1A1A1A") : UIColor(hexStr: "#999999")
self.joinedTabLabel.textColor = isCreated ? UIColor(hexStr: "#999999") : UIColor(hexStr: "#1A1A1A")
self.tabIndicator.center.x = isCreated
? self.createdTabLabel.center.x
: self.joinedTabLabel.center.x
self.layoutIfNeeded()
}
}
override init(frame: CGRect) {
super.init(frame: .zero)
backgroundColor = .white
setupUI()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// MARK: - UIScrollViewDelegate PanView + PanSubItemView
extension GroupView: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == mainScrollView {
handleMainScroll(scrollView)
} else if scrollView == segmentScrollView {
let page = Int(round(scrollView.contentOffset.x / scrollView.bounds.width))
if page != currentSegmentIndex, page >= 0, page < 2 {
currentSegmentIndex = page
selectSegment(at: page)
}
}
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
guard scrollView != mainScrollView, scrollView != segmentScrollView else { return }
if scrollView.contentOffset.y > 0 {
isSubCanScroll = true
}
}
/// GroupViewController rx.didScroll
func handleTableViewScroll(_ scrollView: UIScrollView) {
if isSubCanScroll {
if scrollView.contentOffset.y <= 0 {
isSubCanScroll = false
isSticky = false
scrollView.contentOffset.y = 0
}
} else {
scrollView.contentOffset.y = 0
}
}
// MARK: - scroll
private func handleMainScroll(_ scrollView: UIScrollView) {
if isSubCanScroll {
scrollView.contentOffset.y = stickThreshold
isSticky = true
selectSegment(at: currentSegmentIndex)
} else {
let offsetY = scrollView.contentOffset.y
if offsetY >= stickThreshold {
scrollView.contentOffset.y = stickThreshold
isSticky = true
isSubCanScroll = true
selectSegment(at: currentSegmentIndex)
}
}
}
}
// MARK: - HotGroupCell
final class HotGroupCell: UICollectionViewCell {
static let reuseId = "HotGroupCell"
private let iconView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFill
iv.layer.cornerRadius = 10
iv.clipsToBounds = true
iv.backgroundColor = .clear
return iv
}()
private let nameLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 14, weight: .medium)
label.textColor = ThemeManager.shared.color.titleAuxColor
label.textAlignment = .center
return label
}()
private let countLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 10, weight: .medium)
label.textColor = ThemeManager.shared.color.contentColor
label.textAlignment = .center
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(iconView)
contentView.addSubview(nameLabel)
contentView.addSubview(countLabel)
iconView.layoutChain
.top().centerX()
.width(50).height(50)
nameLabel.layoutChain
.topToBottomOfView(iconView, offset: 6)
.edgesHorzontal()
countLabel.layoutChain
.topToBottomOfView(nameLabel, offset: 2)
.edgesHorzontal()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(_ model: GroupInfoModel) {
iconView.image = model.groupIcon
nameLabel.text = model.name
countLabel.text = "\(model.people_no)个成员"
}
}
// MARK: - CircleGroupCell
final class CircleGroupCell: UITableViewCell {
private let iconView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFill
iv.cornerRadius = 20
iv.clipsToBounds = true
iv.backgroundColor = UIColor(hexStr: "#F0F0F0")
return iv
}()
private let nameLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 14, weight: .medium)
label.textColor = ThemeManager.shared.color.titleAuxColor
return label
}()
private let countLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 10, weight: .medium)
label.textColor = UIColor(hexStr: "#999999")
return label
}()
lazy var unreadBadgeView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(hexStr: "#FF383C")
view.cornerRadius = 7
view.isHidden = true
return view
}()
private let unreadBadge: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 10, weight: .medium)
label.textColor = .white
return label
}()
private let timeLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 10, weight: .regular)
label.textColor = UIColor(hexStr: "#999999")
label.textAlignment = .right
return label
}()
private let dividerLine: UIView = {
let v = UIView()
v.backgroundColor = UIColor(hexStr: "#F0F0F0")
return v
}()
override init(style: CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
backgroundColor = .white
contentView.addSubview(iconView)
contentView.addSubview(nameLabel)
contentView.addSubview(countLabel)
contentView.addSubview(unreadBadgeView)
unreadBadgeView.addSubview(unreadBadge)
contentView.addSubview(timeLabel)
contentView.addSubview(dividerLine)
iconView.layoutChain
.left(15).edgesVertical(15)
.width(40).height(40)
nameLabel.layoutChain
.topToView(iconView, offset: 4)
.leftToRightOfView(iconView, offset: 12)
.right(90)
countLabel.layoutChain
.topToBottomOfView(nameLabel, offset: 4)
.leftToView(nameLabel)
unreadBadgeView.layoutChain
.centerY(nameLabel)
.right(15)
unreadBadge.layoutChain
.edgesHorzontal(8)
.edgesVertical(1)
timeLabel.layoutChain
.right(15)
.centerY(countLabel)
dividerLine.layoutChain
.edgesHorzontal(15)
.bottom()
.height(0.5)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(_ model: GroupCellData) {
let g = model.groupInfo
// if let faceURL = g.faceURL, !faceURL.isEmpty {
// iconView.dl.setImage(with: faceURL)
// } else {
// iconView.image = UIImage(named: "GroupIcon1")
// }
iconView.image = model.localIcon
nameLabel.text = g.groupName ?? ""
countLabel.text = "\(g.memberCount)个成员"
//
if model.unreadCount > 0 {
unreadBadgeView.isHidden = false
unreadBadge.text = model.unreadCount > 99 ? "99+" : "\(model.unreadCount)"
} else {
unreadBadgeView.isHidden = true
}
//
if model.lastMsgTime > 0 {
let date = Date(timeIntervalSince1970: TimeInterval(model.lastMsgTime) / 1000)
timeLabel.text = date.formatForIM()
} else {
timeLabel.text = ""
}
}
}
// MARK: - Date IM format
private extension Date {
func formatForIM() -> String {
let now = Date()
let calendar = Calendar.current
if calendar.isDateInToday(self) {
let f = DateFormatter()
f.dateFormat = "HH:mm"
return f.string(from: self)
} else if calendar.isDateInYesterday(self) {
return "昨天"
} else if calendar.isDate(self, equalTo: now, toGranularity: .year) {
let f = DateFormatter()
f.dateFormat = "MM/dd"
return f.string(from: self)
} else {
let f = DateFormatter()
f.dateFormat = "yyyy/MM/dd"
return f.string(from: self)
}
}
}