搜索页面开发
@ -9,3 +9,6 @@ import UIKit
|
|||||||
|
|
||||||
///登录的用户信息
|
///登录的用户信息
|
||||||
let kSPLoginTokenDefaultsKey = "kSPLoginTokenDefaultsKey"
|
let kSPLoginTokenDefaultsKey = "kSPLoginTokenDefaultsKey"
|
||||||
|
|
||||||
|
///首页搜索记录
|
||||||
|
let kSPHomeSearchHistoryDefaultsKey = "kSPHomeSearchHistoryDefaultsKey"
|
||||||
|
@ -24,7 +24,7 @@ extension UIColor {
|
|||||||
|
|
||||||
|
|
||||||
static func placeholderColor() -> UIColor {
|
static func placeholderColor() -> UIColor {
|
||||||
return .colorFFFFFF(alpha: 0.22)
|
return .colorFFFFFF(alpha: 0.6)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,5 +124,25 @@ extension UIColor {
|
|||||||
static func colorD9D9D9(alpha: CGFloat = 1) -> UIColor {
|
static func colorD9D9D9(alpha: CGFloat = 1) -> UIColor {
|
||||||
return color(hex: 0xD9D9D9, alpha: alpha)
|
return color(hex: 0xD9D9D9, alpha: alpha)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func colorEAF7FF(alpha: CGFloat = 1) -> UIColor {
|
||||||
|
return color(hex: 0xEAF7FF, alpha: alpha)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func colorFF4B5A(alpha: CGFloat = 1) -> UIColor {
|
||||||
|
return color(hex: 0xFF4B5A, alpha: alpha)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func color1E1F2C(alpha: CGFloat = 1) -> UIColor {
|
||||||
|
return color(hex: 0x1E1F2C, alpha: alpha)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func colorE7F5FF(alpha: CGFloat = 1) -> UIColor {
|
||||||
|
return color(hex: 0xE7F5FF, alpha: alpha)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func colorAFB8BF(alpha: CGFloat = 1) -> UIColor {
|
||||||
|
return color(hex: 0xAFB8BF, alpha: alpha)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,5 +73,16 @@ class SPHomeAPI: NSObject {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///热门搜索
|
||||||
|
static func requestHotSearchList(completer: ((_ list: [SPShortModel]?) -> Void)?) {
|
||||||
|
var param = SPNetworkParameters(path: "/search/hots")
|
||||||
|
param.method = .get
|
||||||
|
|
||||||
|
SPNetwork.request(parameters: param) { (response: SPNetworkResponse<SPListModel<SPShortModel>>) in
|
||||||
|
completer?(response.data?.list)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ class SPTextField: UITextField {
|
|||||||
// var iq = self.iq
|
// var iq = self.iq
|
||||||
// iq.enableMode = .enabled
|
// iq.enableMode = .enabled
|
||||||
|
|
||||||
self.textColor = .colorFFFFFF(alpha: 0.9)
|
self.textColor = .colorFFFFFF()
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
|
@ -8,7 +8,11 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class SPSearchViewController: SPViewController {
|
class SPSearchViewController: SPViewController {
|
||||||
|
|
||||||
|
|
||||||
|
private lazy var viewModel: SPSearchViewModel = SPSearchViewModel()
|
||||||
|
|
||||||
|
//MARK: UI属性
|
||||||
private lazy var backButton: UIButton = {
|
private lazy var backButton: UIButton = {
|
||||||
let button = UIButton(type: .custom)
|
let button = UIButton(type: .custom)
|
||||||
button.setImage(UIImage(named: "arrow_left_icon_01"), for: .normal)
|
button.setImage(UIImage(named: "arrow_left_icon_01"), for: .normal)
|
||||||
@ -28,12 +32,18 @@ class SPSearchViewController: SPViewController {
|
|||||||
///搜索首页
|
///搜索首页
|
||||||
private lazy var homeView: SPSearchHomeView = {
|
private lazy var homeView: SPSearchHomeView = {
|
||||||
let view = SPSearchHomeView()
|
let view = SPSearchHomeView()
|
||||||
|
view.viewModel = self.viewModel
|
||||||
|
view.searchTextBlock = { [weak self] text in
|
||||||
|
self?.searchInputView.textField.text = text
|
||||||
|
self?.textDidChange(text: text)
|
||||||
|
}
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
||||||
///联想页面
|
///联想页面
|
||||||
private lazy var associativeView: SPSearchAssociativeView = {
|
private lazy var associativeView: SPSearchAssociativeView = {
|
||||||
let view = SPSearchAssociativeView()
|
let view = SPSearchAssociativeView()
|
||||||
|
view.viewModel = self.viewModel
|
||||||
view.isHidden = true
|
view.isHidden = true
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
@ -48,6 +58,8 @@ class SPSearchViewController: SPViewController {
|
|||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
searchInputView.textField.becomeFirstResponder()
|
||||||
|
|
||||||
_setupUI()
|
_setupUI()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
84
Thimra/Class/Home/View/SPHomeHotSearchCell.swift
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
//
|
||||||
|
// SPHomeHotSearchCell.swift
|
||||||
|
// Thimra
|
||||||
|
//
|
||||||
|
// Created by Overseas on 2025/4/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SPHomeHotSearchCell: SPTableViewCell {
|
||||||
|
|
||||||
|
var model: SPShortModel? {
|
||||||
|
didSet {
|
||||||
|
coverImageView.sp_setImage(url: model?.image_url)
|
||||||
|
|
||||||
|
titleLabel.text = model?.name
|
||||||
|
|
||||||
|
desLabel.text = model?.sp_description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: UI属性
|
||||||
|
private lazy var coverImageView: SPImageView = {
|
||||||
|
let imageView = SPImageView()
|
||||||
|
imageView.layer.cornerRadius = 6
|
||||||
|
imageView.layer.masksToBounds = true
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var titleLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .fontMedium(ofSize: 14)
|
||||||
|
label.textColor = .colorFFFFFF()
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var desLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .fontRegular(ofSize: 12)
|
||||||
|
label.textColor = .colorAFB8BF()
|
||||||
|
label.numberOfLines = 2
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
|
|
||||||
|
_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SPHomeHotSearchCell {
|
||||||
|
|
||||||
|
private func _setupUI() {
|
||||||
|
contentView.addSubview(coverImageView)
|
||||||
|
contentView.addSubview(titleLabel)
|
||||||
|
contentView.addSubview(desLabel)
|
||||||
|
|
||||||
|
coverImageView.snp.makeConstraints { make in
|
||||||
|
make.centerY.equalToSuperview()
|
||||||
|
make.left.equalToSuperview().offset(12)
|
||||||
|
make.width.equalTo(74)
|
||||||
|
make.height.equalTo(100)
|
||||||
|
}
|
||||||
|
|
||||||
|
titleLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(coverImageView.snp.right).offset(12)
|
||||||
|
make.right.lessThanOrEqualToSuperview().offset(-12)
|
||||||
|
make.top.equalTo(coverImageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
desLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(titleLabel)
|
||||||
|
make.top.equalTo(titleLabel.snp.bottom).offset(5)
|
||||||
|
make.right.lessThanOrEqualToSuperview().offset(-12)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
138
Thimra/Class/Home/View/SPHomeHotSearchView.swift
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
//
|
||||||
|
// SPHomeHotSearchView.swift
|
||||||
|
// Thimra
|
||||||
|
//
|
||||||
|
// Created by Overseas on 2025/4/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SPHomeHotSearchView: UIView {
|
||||||
|
|
||||||
|
override var intrinsicContentSize: CGSize {
|
||||||
|
return CGSize(width: kSPScreenWidth, height: kSPScreenHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var dataArr: [SPShortModel] = []
|
||||||
|
|
||||||
|
private lazy var bgView: UIView = {
|
||||||
|
let view = UIImageView(image: UIImage(named: "hot_search_bg_image"))
|
||||||
|
view.isUserInteractionEnabled = true
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var titleLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .fontMedium(ofSize: 16)
|
||||||
|
label.textColor = .colorFFFFFF()
|
||||||
|
label.text = "Top Search".localized
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var hotIconImageView: UIImageView = {
|
||||||
|
let imageView = UIImageView(image: UIImage(named: "hot_icon_02"))
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var tableView: SPTableView = {
|
||||||
|
let tableView = SPTableView(frame: .zero, style: .plain)
|
||||||
|
tableView.delegate = self
|
||||||
|
tableView.dataSource = self
|
||||||
|
tableView.rowHeight = 112
|
||||||
|
tableView.separatorStyle = .none
|
||||||
|
tableView.showsVerticalScrollIndicator = false
|
||||||
|
tableView.showsHorizontalScrollIndicator = false
|
||||||
|
tableView.contentInset = .init(top: 0, left: 0, bottom: kSPTabbarSafeBottomMargin, right: 0)
|
||||||
|
SPHomeHotSearchCell.registerCell(tableView: tableView)
|
||||||
|
return tableView
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
_setupUI()
|
||||||
|
|
||||||
|
requestDataArr()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SPHomeHotSearchView {
|
||||||
|
|
||||||
|
private func _setupUI() {
|
||||||
|
addSubview(bgView)
|
||||||
|
bgView.addSubview(titleLabel)
|
||||||
|
bgView.addSubview(hotIconImageView)
|
||||||
|
bgView.addSubview(tableView)
|
||||||
|
|
||||||
|
bgView.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(16)
|
||||||
|
make.centerY.equalToSuperview()
|
||||||
|
make.top.equalToSuperview()
|
||||||
|
make.bottom.equalToSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
titleLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(12)
|
||||||
|
make.top.equalToSuperview().offset(20)
|
||||||
|
}
|
||||||
|
|
||||||
|
hotIconImageView.snp.makeConstraints { make in
|
||||||
|
make.centerY.equalTo(titleLabel)
|
||||||
|
make.left.equalTo(titleLabel.snp.right).offset(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
tableView.snp.makeConstraints { make in
|
||||||
|
make.left.right.bottom.equalToSuperview()
|
||||||
|
make.top.equalToSuperview().offset(50)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: -------------- UITableViewDelegate & UITableViewDataSource --------------
|
||||||
|
extension SPHomeHotSearchView: UITableViewDelegate, UITableViewDataSource {
|
||||||
|
|
||||||
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
|
let cell = SPHomeHotSearchCell.dequeueReusableCell(tableView: tableView, indexPath: indexPath)
|
||||||
|
cell.model = dataArr[indexPath.row]
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
|
return self.dataArr.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
|
let model = dataArr[indexPath.row]
|
||||||
|
|
||||||
|
let vc = SPPlayerDetailViewController()
|
||||||
|
vc.shortPlayId = model.short_play_id
|
||||||
|
self.viewController?.navigationController?.pushViewController(vc, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SPHomeHotSearchView {
|
||||||
|
|
||||||
|
private func requestDataArr() {
|
||||||
|
|
||||||
|
SPHomeAPI.requestHotSearchList { [weak self] list in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
if let list = list {
|
||||||
|
self.dataArr = list
|
||||||
|
self.tableView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
129
Thimra/Class/Home/View/SPHomeSearchHistoryView.swift
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
//
|
||||||
|
// SPHomeSearchHistoryView.swift
|
||||||
|
// Thimra
|
||||||
|
//
|
||||||
|
// Created by Overseas on 2025/4/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SPHomeSearchHistoryView: UIView {
|
||||||
|
|
||||||
|
|
||||||
|
override var intrinsicContentSize: CGSize {
|
||||||
|
return CGSize(width: kSPScreenWidth, height: 30 + self.tagView.intrinsicContentSize.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
var viewModel: SPSearchViewModel? {
|
||||||
|
didSet {
|
||||||
|
viewModel?.addObserver(self, forKeyPath: "searchHistoryArr", context: nil)
|
||||||
|
|
||||||
|
self.tagView.reloadData()
|
||||||
|
|
||||||
|
self.invalidateIntrinsicContentSize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var searchTextBlock: ((_ text: String) -> Void)?
|
||||||
|
|
||||||
|
private lazy var titleLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .fontBold(ofSize: 16)
|
||||||
|
label.textColor = .colorE7F5FF()
|
||||||
|
label.text = "Historical search".localized
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var deleteButton: UIButton = {
|
||||||
|
let button = UIButton(type: .custom)
|
||||||
|
button.setImage(UIImage(named: "delete_icon_03"), for: .normal)
|
||||||
|
button.addTarget(self, action: #selector(handleDeleteButton), for: .touchUpInside)
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var tagView: JXTagView = {
|
||||||
|
let view = JXTagView()
|
||||||
|
view.delegate = self
|
||||||
|
view.dataSource = self
|
||||||
|
view.tagBorderWidth = 1
|
||||||
|
view.tagBorderColor = .colorFFFFFF(alpha: 0.2)
|
||||||
|
view.tagHeight = 20
|
||||||
|
view.tagCornerRadius = 10
|
||||||
|
view.tagBackgroundColor = .clear
|
||||||
|
view.textFont = .fontRegular(ofSize: 12)
|
||||||
|
view.textColor = .colorFFFFFF(alpha: 0.8)
|
||||||
|
view.textMargin = 8
|
||||||
|
view.tagHorizontalGap = 12
|
||||||
|
view.tagVerticalGap = 12
|
||||||
|
view.leftAndRightMargin = 16
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
viewModel?.removeObserver(self, forKeyPath: "searchHistoryArr")
|
||||||
|
}
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||||
|
if keyPath == "searchHistoryArr" {
|
||||||
|
self.tagView.reloadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@objc private func handleDeleteButton() {
|
||||||
|
self.viewModel?.cleanSearchHistory()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SPHomeSearchHistoryView {
|
||||||
|
|
||||||
|
private func _setupUI() {
|
||||||
|
addSubview(titleLabel)
|
||||||
|
addSubview(deleteButton)
|
||||||
|
addSubview(tagView)
|
||||||
|
|
||||||
|
titleLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(16)
|
||||||
|
make.centerY.equalTo(deleteButton)
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteButton.snp.makeConstraints { make in
|
||||||
|
make.right.equalToSuperview().offset(-16)
|
||||||
|
make.top.equalToSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
tagView.snp.makeConstraints { make in
|
||||||
|
make.left.right.equalToSuperview()
|
||||||
|
make.bottom.equalToSuperview()
|
||||||
|
// make.top.equalTo(self.titleLabel.snp.bottom).offset(12)
|
||||||
|
make.top.equalToSuperview().offset(30)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: -------------- JXTagViewDelegate & JXTagViewDataSource --------------
|
||||||
|
extension SPHomeSearchHistoryView: JXTagViewDelegate, JXTagViewDataSource {
|
||||||
|
func jx_tagView(tagView: JXTagView, titleForIndex index: Int) -> String? {
|
||||||
|
return viewModel?.searchHistoryArr[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
func jx_number(in tagView: JXTagView) -> Int {
|
||||||
|
return viewModel?.searchHistoryArr.count ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func jx_tagView(tagView: JXTagView, didSelectedTagAt index: Int) {
|
||||||
|
guard let text = viewModel?.searchHistoryArr[index] else { return }
|
||||||
|
self.searchTextBlock?(text)
|
||||||
|
}
|
||||||
|
}
|
@ -19,10 +19,10 @@ class SPSearchAssociativeCell: SPTableViewCell {
|
|||||||
titleLabel.attributedText = string
|
titleLabel.attributedText = string
|
||||||
} else {
|
} else {
|
||||||
let string = NSMutableAttributedString(string: model?.name ?? "")
|
let string = NSMutableAttributedString(string: model?.name ?? "")
|
||||||
string.color = .color8A899F()
|
string.color = .colorEAF7FF()
|
||||||
|
|
||||||
if let range = model?.name?.ocString().range(of: searchText ?? "") {
|
if let range = model?.name?.lowercased().ocString().range(of: (searchText ?? "").lowercased()) {
|
||||||
string.setColor(.colorFFFFFF(), range: range)
|
string.setColor(.colorFF4B5A(), range: range)
|
||||||
}
|
}
|
||||||
|
|
||||||
titleLabel.attributedText = string
|
titleLabel.attributedText = string
|
||||||
@ -33,6 +33,8 @@ class SPSearchAssociativeCell: SPTableViewCell {
|
|||||||
|
|
||||||
private lazy var iconImageView: UIImageView = {
|
private lazy var iconImageView: UIImageView = {
|
||||||
let imageView = UIImageView(image: UIImage(named: "play_icon_03"))
|
let imageView = UIImageView(image: UIImage(named: "play_icon_03"))
|
||||||
|
imageView.setContentHuggingPriority(.required, for: .horizontal)
|
||||||
|
imageView.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||||
return imageView
|
return imageView
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -62,12 +64,12 @@ extension SPSearchAssociativeCell {
|
|||||||
|
|
||||||
iconImageView.snp.makeConstraints { make in
|
iconImageView.snp.makeConstraints { make in
|
||||||
make.centerY.equalToSuperview()
|
make.centerY.equalToSuperview()
|
||||||
make.left.equalToSuperview().offset(15)
|
make.left.equalToSuperview().offset(16)
|
||||||
}
|
}
|
||||||
|
|
||||||
titleLabel.snp.makeConstraints { make in
|
titleLabel.snp.makeConstraints { make in
|
||||||
make.centerY.equalToSuperview()
|
make.centerY.equalToSuperview()
|
||||||
make.left.equalToSuperview().offset(40)
|
make.left.equalToSuperview().offset(36)
|
||||||
make.right.lessThanOrEqualToSuperview().offset(-15)
|
make.right.lessThanOrEqualToSuperview().offset(-15)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import UIKit
|
|||||||
|
|
||||||
class SPSearchAssociativeView: UIView {
|
class SPSearchAssociativeView: UIView {
|
||||||
|
|
||||||
|
var viewModel: SPSearchViewModel?
|
||||||
|
|
||||||
private(set) lazy var searchText: String = ""
|
private(set) lazy var searchText: String = ""
|
||||||
|
|
||||||
@ -18,8 +19,11 @@ class SPSearchAssociativeView: UIView {
|
|||||||
let tableView = SPTableView(frame: .zero, style: .plain)
|
let tableView = SPTableView(frame: .zero, style: .plain)
|
||||||
tableView.delegate = self
|
tableView.delegate = self
|
||||||
tableView.dataSource = self
|
tableView.dataSource = self
|
||||||
tableView.rowHeight = 50
|
tableView.rowHeight = 60
|
||||||
|
tableView.separatorColor = .color1E1F2C()
|
||||||
|
tableView.separatorInset = .init(top: 0, left: 16, bottom: 0, right: 16)
|
||||||
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: kSPTabbarSafeBottomMargin, right: 0)
|
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: kSPTabbarSafeBottomMargin, right: 0)
|
||||||
|
tableView.keyboardDismissMode = .onDrag
|
||||||
SPSearchAssociativeCell.registerCell(tableView: tableView)
|
SPSearchAssociativeCell.registerCell(tableView: tableView)
|
||||||
return tableView
|
return tableView
|
||||||
}()
|
}()
|
||||||
@ -71,6 +75,7 @@ extension SPSearchAssociativeView: UITableViewDelegate, UITableViewDataSource {
|
|||||||
|
|
||||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
let model = self.dataArr[indexPath.row]
|
let model = self.dataArr[indexPath.row]
|
||||||
|
self.viewModel?.addSearchHistory(text: self.searchText)
|
||||||
|
|
||||||
let vc = SPPlayerDetailViewController()
|
let vc = SPPlayerDetailViewController()
|
||||||
vc.shortPlayId = model.short_play_id
|
vc.shortPlayId = model.short_play_id
|
||||||
|
@ -8,7 +8,89 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class SPSearchHomeView: UIView {
|
class SPSearchHomeView: UIView {
|
||||||
|
|
||||||
|
|
||||||
|
var viewModel: SPSearchViewModel? {
|
||||||
|
didSet {
|
||||||
|
viewModel?.addObserver(self, forKeyPath: "searchHistoryArr", context: nil)
|
||||||
|
|
||||||
|
historyView.viewModel = self.viewModel
|
||||||
|
|
||||||
|
_updateUI()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var searchTextBlock: ((_ text: String) -> Void)?
|
||||||
|
|
||||||
|
//MARK: UI属性
|
||||||
|
private lazy var stackView: UIStackView = {
|
||||||
|
let view = UIStackView()
|
||||||
|
view.axis = .vertical
|
||||||
|
view.spacing = 28
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var historyView: SPHomeSearchHistoryView = {
|
||||||
|
let view = SPHomeSearchHistoryView()
|
||||||
|
view.searchTextBlock = { [weak self] text in
|
||||||
|
self?.searchTextBlock?(text)
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
///热门搜索
|
||||||
|
private lazy var hotSearchView: SPHomeHotSearchView = {
|
||||||
|
let view = SPHomeHotSearchView()
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
viewModel?.removeObserver(self, forKeyPath: "searchHistoryArr")
|
||||||
|
}
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
stackView.addArrangedSubview(historyView)
|
||||||
|
|
||||||
|
_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||||
|
if keyPath == "searchHistoryArr" {
|
||||||
|
_updateUI()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SPSearchHomeView {
|
||||||
|
|
||||||
|
private func _setupUI() {
|
||||||
|
addSubview(stackView)
|
||||||
|
|
||||||
|
stackView.snp.makeConstraints { make in
|
||||||
|
make.top.equalToSuperview().offset(10)
|
||||||
|
make.left.right.bottom.equalToSuperview()
|
||||||
|
// make.edges.equalToSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func _updateUI() {
|
||||||
|
stackView.removeAllArrangedSubview()
|
||||||
|
|
||||||
|
if (self.viewModel?.searchHistoryArr.count ?? 0) > 0 {
|
||||||
|
self.stackView.addArrangedSubview(self.historyView)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.stackView.addArrangedSubview(self.hotSearchView)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import UIKit
|
|||||||
class SPSearchInputView: UIView {
|
class SPSearchInputView: UIView {
|
||||||
|
|
||||||
override var intrinsicContentSize: CGSize {
|
override var intrinsicContentSize: CGSize {
|
||||||
return CGSize(width: kSPScreenWidth, height: 38)
|
return CGSize(width: kSPScreenWidth, height: 36)
|
||||||
}
|
}
|
||||||
|
|
||||||
var placeholder: String? {
|
var placeholder: String? {
|
||||||
@ -31,7 +31,8 @@ class SPSearchInputView: UIView {
|
|||||||
|
|
||||||
private(set) lazy var textField: SPTextField = {
|
private(set) lazy var textField: SPTextField = {
|
||||||
let textField = SPTextField()
|
let textField = SPTextField()
|
||||||
textField.font = .fontRegular(ofSize: 12)
|
textField.font = .fontMedium(ofSize: 14)
|
||||||
|
textField.sp_placeholderFont = .fontRegular(ofSize: 14)
|
||||||
textField.returnKeyType = .search
|
textField.returnKeyType = .search
|
||||||
textField.textDidChange = { [weak self] text in
|
textField.textDidChange = { [weak self] text in
|
||||||
self?.textDidChange?(text)
|
self?.textDidChange?(text)
|
||||||
@ -41,9 +42,12 @@ class SPSearchInputView: UIView {
|
|||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
layer.cornerRadius = 19
|
layer.cornerRadius = 8
|
||||||
layer.masksToBounds = true
|
layer.masksToBounds = true
|
||||||
backgroundColor = .colorFFFFFF(alpha: 0.1)
|
backgroundColor = .colorFFFFFF(alpha: 0.2)
|
||||||
|
|
||||||
|
setContentHuggingPriority(.required, for: .vertical)
|
||||||
|
setContentCompressionResistancePriority(.required, for: .vertical)
|
||||||
|
|
||||||
_setupUI()
|
_setupUI()
|
||||||
|
|
||||||
@ -62,13 +66,13 @@ extension SPSearchInputView {
|
|||||||
addSubview(textField)
|
addSubview(textField)
|
||||||
|
|
||||||
iconImageView.snp.makeConstraints { make in
|
iconImageView.snp.makeConstraints { make in
|
||||||
make.left.equalToSuperview().offset(15)
|
make.left.equalToSuperview().offset(16)
|
||||||
make.centerY.equalToSuperview()
|
make.centerY.equalToSuperview()
|
||||||
}
|
}
|
||||||
|
|
||||||
textField.snp.makeConstraints { make in
|
textField.snp.makeConstraints { make in
|
||||||
make.top.bottom.equalToSuperview()
|
make.top.bottom.equalToSuperview()
|
||||||
make.left.equalTo(iconImageView.snp.right).offset(7)
|
make.left.equalTo(iconImageView.snp.right).offset(8)
|
||||||
make.right.equalToSuperview().offset(-15)
|
make.right.equalToSuperview().offset(-15)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,4 +14,6 @@ class SPHomeViewModel: NSObject {
|
|||||||
///历史记录
|
///历史记录
|
||||||
var playHistoryArr: [SPShortModel]?
|
var playHistoryArr: [SPShortModel]?
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,53 @@ import UIKit
|
|||||||
|
|
||||||
class SPSearchViewModel: NSObject {
|
class SPSearchViewModel: NSObject {
|
||||||
|
|
||||||
|
///搜索历史
|
||||||
|
@objc dynamic var searchHistoryArr: [String] = SPSearchViewModel.getSearchHistory()
|
||||||
|
|
||||||
|
|
||||||
|
func addSearchHistory(text: String) {
|
||||||
|
guard text.count > 0 else { return }
|
||||||
|
|
||||||
|
SPSearchViewModel.addSearchHistory(text: text)
|
||||||
|
searchHistoryArr = SPSearchViewModel.getSearchHistory()
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanSearchHistory() {
|
||||||
|
SPSearchViewModel.cleanSearchHistory()
|
||||||
|
searchHistoryArr.removeAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SPSearchViewModel {
|
||||||
|
|
||||||
|
///添加历史记录
|
||||||
|
static func addSearchHistory(text: String) {
|
||||||
|
var arr = getSearchHistory()
|
||||||
|
for (i, value) in arr.enumerated() {
|
||||||
|
if value == text {
|
||||||
|
arr.remove(at: i)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
arr.insert(text, at: 0)
|
||||||
|
if arr.count > 10 {
|
||||||
|
arr.removeLast()
|
||||||
|
}
|
||||||
|
UserDefaults.standard.set(arr, forKey: kSPHomeSearchHistoryDefaultsKey)
|
||||||
|
UserDefaults.standard.synchronize()
|
||||||
|
}
|
||||||
|
|
||||||
|
///获取历史记录
|
||||||
|
static func getSearchHistory() -> [String] {
|
||||||
|
let arr = UserDefaults.standard.object(forKey: kSPHomeSearchHistoryDefaultsKey) as? [String]
|
||||||
|
return arr ?? []
|
||||||
|
}
|
||||||
|
|
||||||
|
///清空历史记录
|
||||||
|
static func cleanSearchHistory() {
|
||||||
|
UserDefaults.standard.set([], forKey: kSPHomeSearchHistoryDefaultsKey)
|
||||||
|
UserDefaults.standard.synchronize()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,11 +5,12 @@
|
|||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "nav_back@2x.png",
|
"filename" : "Frame 70@2x.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"filename" : "Frame 70@3x.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "3x"
|
"scale" : "3x"
|
||||||
}
|
}
|
||||||
|
BIN
Thimra/Source/Assets.xcassets/icon/arrow_left_icon_01.imageset/Frame 70@2x.png
vendored
Normal file
After Width: | Height: | Size: 245 B |
BIN
Thimra/Source/Assets.xcassets/icon/arrow_left_icon_01.imageset/Frame 70@3x.png
vendored
Normal file
After Width: | Height: | Size: 264 B |
Before Width: | Height: | Size: 116 B |
22
Thimra/Source/Assets.xcassets/icon/delete_icon_03.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Frame@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Frame@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Thimra/Source/Assets.xcassets/icon/delete_icon_03.imageset/Frame@2x.png
vendored
Normal file
After Width: | Height: | Size: 471 B |
BIN
Thimra/Source/Assets.xcassets/icon/delete_icon_03.imageset/Frame@3x.png
vendored
Normal file
After Width: | Height: | Size: 632 B |
22
Thimra/Source/Assets.xcassets/icon/hot_icon_02.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Frame@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Frame@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Thimra/Source/Assets.xcassets/icon/hot_icon_02.imageset/Frame@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
Thimra/Source/Assets.xcassets/icon/hot_icon_02.imageset/Frame@3x.png
vendored
Normal file
After Width: | Height: | Size: 2.0 KiB |
@ -5,12 +5,12 @@
|
|||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "play@2x.png",
|
"filename" : "Frame 71@2x.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "play@3x.png",
|
"filename" : "Frame 71@3x.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "3x"
|
"scale" : "3x"
|
||||||
}
|
}
|
||||||
|
BIN
Thimra/Source/Assets.xcassets/icon/play_icon_03.imageset/Frame 71@2x.png
vendored
Normal file
After Width: | Height: | Size: 659 B |
BIN
Thimra/Source/Assets.xcassets/icon/play_icon_03.imageset/Frame 71@3x.png
vendored
Normal file
After Width: | Height: | Size: 860 B |
Before Width: | Height: | Size: 895 B |
Before Width: | Height: | Size: 1.5 KiB |
@ -5,12 +5,12 @@
|
|||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "搜索图标@2x.png",
|
"filename" : "Frame 26@2x.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "搜索图标@3x.png",
|
"filename" : "Frame 26@3x.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "3x"
|
"scale" : "3x"
|
||||||
}
|
}
|
||||||
|
BIN
Thimra/Source/Assets.xcassets/icon/search_icon_02.imageset/Frame 26@2x.png
vendored
Normal file
After Width: | Height: | Size: 636 B |
BIN
Thimra/Source/Assets.xcassets/icon/search_icon_02.imageset/Frame 26@3x.png
vendored
Normal file
After Width: | Height: | Size: 933 B |
Before Width: | Height: | Size: 1000 B |
Before Width: | Height: | Size: 1.8 KiB |
22
Thimra/Source/Assets.xcassets/image/hot_search_bg_image.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "背景图@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "背景图@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Thimra/Source/Assets.xcassets/image/hot_search_bg_image.imageset/背景图@2x.png
vendored
Normal file
After Width: | Height: | Size: 505 KiB |
BIN
Thimra/Source/Assets.xcassets/image/hot_search_bg_image.imageset/背景图@3x.png
vendored
Normal file
After Width: | Height: | Size: 1011 KiB |
@ -38,6 +38,8 @@
|
|||||||
"Continue watching" = "Continue watching";
|
"Continue watching" = "Continue watching";
|
||||||
"More for you!" = "More for you!";
|
"More for you!" = "More for you!";
|
||||||
"More" = "More";
|
"More" = "More";
|
||||||
|
"Historical search" = "Historical search";
|
||||||
|
"Top Search" = "Top Search";
|
||||||
|
|
||||||
///视频详情标题
|
///视频详情标题
|
||||||
"kPlayerDetailTitleString" = "Episode %@ / %@";
|
"kPlayerDetailTitleString" = "Episode %@ / %@";
|
||||||
|
307
Thimra/Thirdparty/JXTagView/JXTagView.swift
vendored
Normal file
@ -0,0 +1,307 @@
|
|||||||
|
//
|
||||||
|
// JXTagView.swift
|
||||||
|
// YDLive
|
||||||
|
//
|
||||||
|
// Created by 曾觉新 on 2020/11/7.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@objc protocol JXTagViewDataSource: NSObjectProtocol {
|
||||||
|
|
||||||
|
@objc optional func jx_tagView(tagView: JXTagView, titleForIndex index: Int) -> String?
|
||||||
|
@objc optional func jx_tagView(tagView: JXTagView, attributedTitleForIndex index: Int) -> NSAttributedString?
|
||||||
|
func jx_number(in tagView: JXTagView) -> Int
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc protocol JXTagViewDelegate: NSObjectProtocol {
|
||||||
|
|
||||||
|
@objc optional func jx_tagView(tagView: JXTagView, didSelectedTagAt index: Int)
|
||||||
|
|
||||||
|
@objc optional func jx_tagView(tagView: JXTagView, enableForIndex index: Int) -> Bool
|
||||||
|
|
||||||
|
@objc optional func jx_tagView(tagView: JXTagView, selectedForIndex index: Int) -> Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
class JXTagView: UIView {
|
||||||
|
enum LayoutDirection: Int {
|
||||||
|
case vertical = 0
|
||||||
|
case horizontal = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
weak var delegate: JXTagViewDelegate?
|
||||||
|
weak var dataSource: JXTagViewDataSource?
|
||||||
|
|
||||||
|
var layoutDirection: LayoutDirection = .vertical
|
||||||
|
|
||||||
|
var tagBackgroundColor: UIColor = .systemBackground
|
||||||
|
var tagSelectedBackgroundColor: UIColor = .systemBackground
|
||||||
|
var tagDisabledBackgroundColor: UIColor = .systemBackground
|
||||||
|
|
||||||
|
var textFont: UIFont = .systemFont(ofSize: 14)
|
||||||
|
|
||||||
|
var textColor: UIColor = .colorFFFFFF()
|
||||||
|
var textSelectedColor: UIColor = .colorFFFFFF()
|
||||||
|
var textDisabledColor: UIColor = .gray
|
||||||
|
|
||||||
|
var tagCornerRadius: CGFloat = 14
|
||||||
|
|
||||||
|
var tagBorderWidth: CGFloat = 0
|
||||||
|
var tagBorderColor: UIColor?
|
||||||
|
var tagBorderSelectedColor: UIColor?
|
||||||
|
|
||||||
|
/// 0表示自适应
|
||||||
|
var tagWidth: CGFloat = 0
|
||||||
|
var tagHeight: CGFloat = 28
|
||||||
|
var tagMinWidth: CGFloat = 0
|
||||||
|
///文本边距
|
||||||
|
var textMargin: CGFloat = 0
|
||||||
|
///横向间隙
|
||||||
|
var tagHorizontalGap: CGFloat = 10
|
||||||
|
///纵向间隙
|
||||||
|
var tagVerticalGap: CGFloat = 10
|
||||||
|
///左右边距
|
||||||
|
var leftAndRightMargin: CGFloat = 20
|
||||||
|
///上下边距
|
||||||
|
var topAndBottomMargin: CGFloat = 0
|
||||||
|
///当前选中的标签
|
||||||
|
private(set) var selectedIndex: Int?
|
||||||
|
|
||||||
|
private var oneReload = true
|
||||||
|
|
||||||
|
//MARK:-------------- 黄金分割线 --------------
|
||||||
|
private(set) var buttonArr: [UIButton] = []
|
||||||
|
|
||||||
|
private var contentHeight: CGFloat = 0
|
||||||
|
|
||||||
|
override var intrinsicContentSize: CGSize {
|
||||||
|
let height = (self.buttonArr.last?.frame.maxY ?? 0) + self.topAndBottomMargin
|
||||||
|
if layoutDirection == .vertical {
|
||||||
|
return CGSize(width: UIScreen.main.bounds.size.width, height: height)
|
||||||
|
} else {
|
||||||
|
let width = (self.buttonArr.last?.frame.maxX ?? 0) + self.leftAndRightMargin
|
||||||
|
return CGSize(width: width, height: height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deinit {
|
||||||
|
// NotificationCenter.default.removeObserver(self)
|
||||||
|
// }
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
if oneReload {
|
||||||
|
self.reloadData()
|
||||||
|
oneReload = false
|
||||||
|
}
|
||||||
|
|
||||||
|
self.buttonArr.enumerated().forEach {
|
||||||
|
let isEnble: Bool = self.delegate?.jx_tagView?(tagView: self, enableForIndex: $0) ?? true
|
||||||
|
let isSelected: Bool = self.delegate?.jx_tagView?(tagView: self, selectedForIndex: $0) ?? false
|
||||||
|
|
||||||
|
let button = $1
|
||||||
|
button.isSelected = isSelected
|
||||||
|
button.isEnabled = isEnble
|
||||||
|
|
||||||
|
if isSelected {
|
||||||
|
button.layer.borderColor = tagBorderSelectedColor?.cgColor
|
||||||
|
} else {
|
||||||
|
button.layer.borderColor = tagBorderColor?.cgColor
|
||||||
|
}
|
||||||
|
button.setBackgroundImage(UIImage(color: tagBackgroundColor), for: .normal)
|
||||||
|
button.setBackgroundImage(UIImage(color: tagSelectedBackgroundColor), for: .selected)
|
||||||
|
button.setBackgroundImage(UIImage(color: tagSelectedBackgroundColor), for: [.highlighted, .selected])
|
||||||
|
button.setBackgroundImage(UIImage(color: tagDisabledBackgroundColor), for: .disabled)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///数据没有变化,只是变化状态
|
||||||
|
func updateState() {
|
||||||
|
self.setNeedsLayout()
|
||||||
|
self.layoutIfNeeded()
|
||||||
|
}
|
||||||
|
|
||||||
|
///重置布局:当数据发生变化时,需要重置布局
|
||||||
|
@objc func reloadData() {
|
||||||
|
selectedIndex = nil
|
||||||
|
for button in buttonArr {
|
||||||
|
button.removeFromSuperview()
|
||||||
|
}
|
||||||
|
buttonArr.removeAll()
|
||||||
|
|
||||||
|
let number = self.dataSource?.jx_number(in: self) ?? 0
|
||||||
|
if number == 0 {
|
||||||
|
self.invalidateIntrinsicContentSize()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for index in 0...(number - 1) {
|
||||||
|
let text = self.dataSource?.jx_tagView?(tagView: self, titleForIndex: index)
|
||||||
|
let attributedTitle = self.dataSource?.jx_tagView?(tagView: self, attributedTitleForIndex: index)
|
||||||
|
let isEnble: Bool = self.delegate?.jx_tagView?(tagView: self, enableForIndex: index) ?? true
|
||||||
|
let isSelected: Bool = self.delegate?.jx_tagView?(tagView: self, selectedForIndex: index) ?? false
|
||||||
|
|
||||||
|
|
||||||
|
let button = UIButton(type: .custom)
|
||||||
|
button.tag = index
|
||||||
|
button.setTitle(text, for: .normal)
|
||||||
|
button.setAttributedTitle(attributedTitle, for: .normal)
|
||||||
|
button.setTitleColor(textColor, for: .normal)
|
||||||
|
button.setTitleColor(textSelectedColor, for: .selected)
|
||||||
|
button.setTitleColor(textSelectedColor, for: [.highlighted, .selected])
|
||||||
|
button.setTitleColor(textDisabledColor, for: .disabled)
|
||||||
|
button.titleLabel?.font = textFont
|
||||||
|
button.layer.cornerRadius = tagCornerRadius
|
||||||
|
button.layer.masksToBounds = true
|
||||||
|
button.layer.borderWidth = tagBorderWidth
|
||||||
|
button.addTarget(self, action: #selector(handleButton(sender:)), for: .touchUpInside)
|
||||||
|
button.isEnabled = isEnble
|
||||||
|
button.titleLabel?.lineBreakMode = .byTruncatingTail
|
||||||
|
button.titleEdgeInsets = UIEdgeInsets(top: 0, left: textMargin, bottom: 0, right: textMargin)
|
||||||
|
|
||||||
|
//修改选中状态
|
||||||
|
button.isSelected = isSelected
|
||||||
|
|
||||||
|
if isSelected {
|
||||||
|
selectedIndex = index
|
||||||
|
}
|
||||||
|
|
||||||
|
self.addSubview(button)
|
||||||
|
self.buttonArr.append(button)
|
||||||
|
}
|
||||||
|
self.updateLayout()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///更新布局
|
||||||
|
func updateLayout() {
|
||||||
|
var x = leftAndRightMargin
|
||||||
|
var y = topAndBottomMargin
|
||||||
|
let height = tagHeight
|
||||||
|
var width: CGFloat = 0
|
||||||
|
|
||||||
|
for button in buttonArr {
|
||||||
|
if tagWidth <= 0 {
|
||||||
|
if let string = button.currentTitle {
|
||||||
|
width = string.size(font: textFont).width + (textMargin * 2)
|
||||||
|
} else if let string = button.currentAttributedTitle {
|
||||||
|
// width = string.size(font: textFont).width + (textMargin * 2)
|
||||||
|
width = string.size().width + textMargin * 2
|
||||||
|
}
|
||||||
|
|
||||||
|
//限制宽度不能超出屏幕
|
||||||
|
if layoutDirection == .vertical {
|
||||||
|
if width > self.width - (leftAndRightMargin * 2) {
|
||||||
|
width = self.width - (leftAndRightMargin * 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//限制不能小于圆弧大小
|
||||||
|
if width < tagCornerRadius * 2 {
|
||||||
|
width = tagCornerRadius * 2
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
width = tagWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
if width < tagMinWidth {
|
||||||
|
width = tagMinWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
if layoutDirection == .vertical {
|
||||||
|
//判断是否需要换行
|
||||||
|
if Float(x + width + leftAndRightMargin) > Float(self.width) {
|
||||||
|
x = leftAndRightMargin
|
||||||
|
y = y + height + tagVerticalGap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
button.frame = CGRect(x: x, y: y, width: width, height: height)
|
||||||
|
|
||||||
|
x = x + width + tagHorizontalGap
|
||||||
|
}
|
||||||
|
self.invalidateIntrinsicContentSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@objc func handleButton(sender: UIButton) {
|
||||||
|
self.delegate?.jx_tagView?(tagView: self, didSelectedTagAt: sender.tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||||
|
super.traitCollectionDidChange(previousTraitCollection)
|
||||||
|
updateState()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// private func _setButtonBackgroundColor(button: UIButton, isSelected: Bool, isEnble: Bool) {
|
||||||
|
// if !isEnble {
|
||||||
|
// button.backgroundColor = tagDisabledBackgroundColor
|
||||||
|
// } else if isSelected {
|
||||||
|
// button.backgroundColor = tagSelectedBackgroundColor
|
||||||
|
// } else {
|
||||||
|
// button.backgroundColor = tagBackgroundColor
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if isSelected {
|
||||||
|
// button.layer.borderColor = tagBorderSelectedColor?.cgColor
|
||||||
|
// } else {
|
||||||
|
// button.layer.borderColor = tagBorderColor?.cgColor
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
extension UIButton {
|
||||||
|
// private struct AssociatedKeys {
|
||||||
|
// static var borderColors: Int?
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func jx_setBackgroundImage(_ image: UIImage?, for state: UIControl.State) {
|
||||||
|
self.setBackgroundImage(image, for: state)
|
||||||
|
if state == .selected {
|
||||||
|
self.setBackgroundImage(image, for: [state, .highlighted])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func jx_setImage(_ image: UIImage?, for state: UIControl.State) {
|
||||||
|
self.setImage(image, for: state)
|
||||||
|
if state == .selected {
|
||||||
|
self.setImage(image, for: [state, .highlighted])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func jx_setTitle(_ title: String?, for state: UIControl.State) {
|
||||||
|
self.setTitle(title, for: state)
|
||||||
|
if state == .selected {
|
||||||
|
self.setTitle(title, for: [state, .highlighted])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func jx_setTitleColor(_ color: UIColor?, for state: UIControl.State) {
|
||||||
|
self.setTitleColor(color, for: state)
|
||||||
|
if state == .selected {
|
||||||
|
self.setTitleColor(color, for: [state, .highlighted])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|