jsdw_ios/QuickLocation/Section/Group/GroupView.swift

576 lines
18 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.

//
// GroupView.swift
// QuickLocation
//
// Created by on 2026/5/29.
//
import UIKit
import RxSwift
import RxCocoa
import SDCycleScrollView
class GroupView: UIView {
var disposeBag = DisposeBag()
// MARK: - Scroll State ( ShopDetailNestView)
enum ScrollState { case pending, scrolling, ended }
var scrollState: ScrollState = .pending
/// segment Y contentView
var segmentY: CGFloat {
let bannerH = bounds.width * (100.0 / 375.0)
return 10 + bannerH + 12 + 60 + 12 + 16 + 8 + 140 + 4
}
/// mainScrollView
var stickThreshold: CGFloat {
return segmentY - kNaviHeight
}
private func setupUI() {
addSubview(navBgView)
navBgView.addSubview(navTitleLabel)
navBgView.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)
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)
// /
let btnWidth = (UIScreen.main.bounds.width - 48) / 2
actionButtonsView.layoutChain
.topToBottomOfView(cycleScrollView, offset: 12)
.edgesHorzontal()
.height(60)
createGroupBtn.layoutChain
.left(16).centerY()
.width(btnWidth).height(44)
joinGroupBtn.layoutChain
.right(16).centerY()
.width(btnWidth).height(44)
//
hotGroupTitleLabel.layoutChain
.topToBottomOfView(actionButtonsView, offset: 12)
.left(16)
hotGroupsCollectionView.layoutChain
.topToBottomOfView(hotGroupTitleLabel, offset: 8)
.edgesHorzontal()
.height(140)
// Segment
let labelWidth = (UIScreen.main.bounds.width - 60) / 2
segmentView.layoutChain
.topToBottomOfView(hotGroupsCollectionView, offset: 4)
.edgesHorzontal()
.height(44)
createdTabLabel.layoutChain
.left(30).centerY()
.width(labelWidth)
joinedTabLabel.layoutChain
.right(30).centerY()
.width(labelWidth)
tabIndicator.layoutChain
.bottom(2).centerX(createdTabLabel)
.width(30).height(3)
// segmentScrollView
segmentScrollView.layoutChain
.topToBottomOfView(segmentView)
.edgesHorzontal()
.bottom()
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)
let minContentHeight = segmentY + bounds.height
if contentView.frame.height < minContentHeight {
contentView.layoutChain.height(minContentHeight)
}
}
// MARK: - Nav
lazy var navBgView: UIImageView = {
let iv = UIImageView()
iv.image = UIImage(named: "Common/navBar_bg_1")
iv.contentMode = .scaleAspectFill
return iv
}()
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: UIScrollView = {
let sv = UIScrollView()
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.localizationImageNamesGroup = ["map_avatar_1", "map_avatar_2", "map_avatar_3"]
view.clipsToBounds = false
view.pageControlBottomOffset = -34
return view
}()
// MARK: - /
private lazy var actionButtonsView: UIView = {
let v = UIView()
v.backgroundColor = .white
return v
}()
private(set) lazy var createGroupBtn: UIView = {
return makeActionButton(title: "创建圈子", icon: "Home/group_icon")
}()
private(set) lazy var joinGroupBtn: UIView = {
return makeActionButton(title: "加入圈子", icon: "Home/group")
}()
private func makeActionButton(title: String, icon: String) -> UIView {
let v = UIView()
v.backgroundColor = UIColor(hexStr: "#F5F7FA")
v.layer.cornerRadius = 12
v.isUserInteractionEnabled = true
let imgView = UIImageView(image: UIImage(named: icon))
imgView.contentMode = .scaleAspectFit
v.addSubview(imgView)
let label = UILabel()
label.text = title
label.font = .systemFont(ofSize: 14, weight: .medium)
label.textColor = UIColor(hexStr: "#1A1A1A")
v.addSubview(label)
imgView.layoutChain
.left(16).centerY()
.width(24).height(24)
label.layoutChain
.leftToRightOfView(imgView, offset: 8)
.centerY()
return v
}
// MARK: -
private lazy var hotGroupTitleLabel: UILabel = {
let label = UILabel()
label.text = "热门酷圈"
label.font = .systemFont(ofSize: 16, weight: .bold)
label.textColor = UIColor(hexStr: "#1A1A1A")
return label
}()
private(set) lazy var hotGroupsCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.itemSize = CGSize(width: 120, height: 140)
layout.minimumLineSpacing = 12
layout.sectionInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)
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
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: -
private(set) lazy var segmentScrollView: UIScrollView = {
let sv = UIScrollView()
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: - (使 PagerTableView )
private(set) lazy var createdTableView: PagerTableView = {
let tv = PagerTableView(frame: .zero, style: .plain)
tv.backgroundColor = .clear
tv.separatorStyle = .none
tv.showsVerticalScrollIndicator = false
tv.register(CircleGroupCell.self)
tv.rowHeight = 72
tv.bounces = true
tv.panDelegate = self
return tv
}()
private(set) lazy var joinedTableView: PagerTableView = {
let tv = PagerTableView(frame: .zero, style: .plain)
tv.backgroundColor = .clear
tv.separatorStyle = .none
tv.showsVerticalScrollIndicator = false
tv.register(CircleGroupCell.self)
tv.rowHeight = 72
tv.bounces = true
tv.panDelegate = self
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")
}
/// tableView GroupViewController Rx
func childTableViewDidScroll(_ scrollView: UIScrollView) {
self.scrollViewDidScroll(scrollView)
}
}
// MARK: - UIScrollViewDelegate + UIGestureRecognizerDelegate
extension GroupView: UIScrollViewDelegate, UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
// mainScrollView tableView
if otherGestureRecognizer == mainScrollView.panGestureRecognizer {
return true
}
return false
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == mainScrollView {
let offsetY = scrollView.contentOffset.y
if offsetY >= stickThreshold {
// mainScrollView tableView
scrollState = .ended
scrollView.contentOffset.y = stickThreshold
scrollView.isScrollEnabled = false
selectSegment(at: currentSegmentIndex)
} else if offsetY > 0 {
scrollState = .scrolling
} else {
scrollState = .pending
scrollView.contentOffset.y = 0
}
} else if scrollView == segmentScrollView {
// segment
let page = Int(round(scrollView.contentOffset.x / scrollView.bounds.width))
if page != currentSegmentIndex && page >= 0 && page < 2 {
currentSegmentIndex = page
selectSegment(at: page)
}
} else {
// tableView
if scrollState == .scrolling {
scrollView.contentOffset = .zero
} else if scrollState == .ended {
if scrollView.contentOffset.y <= 0 {
scrollView.contentOffset.y = 0
scrollState = .pending
mainScrollView.isScrollEnabled = true
// mainScrollView
mainScrollView.contentOffset.y = stickThreshold - 20
}
}
}
}
}
// MARK: - HotGroupCell
final class HotGroupCell: UICollectionViewCell {
static let reuseId = "HotGroupCell"
private let iconView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFill
iv.layer.cornerRadius = 8
iv.clipsToBounds = true
iv.backgroundColor = UIColor(hexStr: "#F0F0F0")
return iv
}()
private let nameLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 13, weight: .medium)
label.textColor = UIColor(hexStr: "#1A1A1A")
label.textAlignment = .center
return label
}()
private let countLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 11, weight: .regular)
label.textColor = UIColor(hexStr: "#999999")
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(60).height(60)
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(_ item: HotGroupItem) {
iconView.image = UIImage(named: item.icon)
nameLabel.text = item.name
countLabel.text = "\(item.memberCount)人·\(item.onlineCount)在线"
}
}
// MARK: - CircleGroupCell
final class CircleGroupCell: UITableViewCell {
private let iconView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFill
iv.layer.cornerRadius = 24
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 = UIColor(hexStr: "#1A1A1A")
return label
}()
private let countLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 12, weight: .regular)
label.textColor = UIColor(hexStr: "#999999")
return label
}()
private let addressLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 12, weight: .regular)
label.textColor = UIColor(hexStr: "#999999")
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(addressLabel)
contentView.addSubview(dividerLine)
iconView.layoutChain
.left(16).centerY()
.width(48).height(48)
nameLabel.layoutChain
.top(12).leftToRightOfView(iconView, offset: 12)
countLabel.layoutChain
.topToBottomOfView(nameLabel, offset: 4)
.leftToView(nameLabel)
addressLabel.layoutChain
.centerY().right(16)
dividerLine.layoutChain
.left(76).right().bottom().height(0.5)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(_ item: CircleGroupItem) {
iconView.image = UIImage(named: item.icon)
nameLabel.text = item.name
countLabel.text = "\(item.memberCount)人·\(item.onlineCount)在线"
addressLabel.text = item.address
}
}