搜索页面开发
@ -9,3 +9,6 @@ import UIKit
|
||||
|
||||
///登录的用户信息
|
||||
let kSPLoginTokenDefaultsKey = "kSPLoginTokenDefaultsKey"
|
||||
|
||||
///首页搜索记录
|
||||
let kSPHomeSearchHistoryDefaultsKey = "kSPHomeSearchHistoryDefaultsKey"
|
||||
|
@ -24,7 +24,7 @@ extension 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 {
|
||||
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
|
||||
// iq.enableMode = .enabled
|
||||
|
||||
self.textColor = .colorFFFFFF(alpha: 0.9)
|
||||
self.textColor = .colorFFFFFF()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
|
@ -9,6 +9,10 @@ import UIKit
|
||||
|
||||
class SPSearchViewController: SPViewController {
|
||||
|
||||
|
||||
private lazy var viewModel: SPSearchViewModel = SPSearchViewModel()
|
||||
|
||||
//MARK: UI属性
|
||||
private lazy var backButton: UIButton = {
|
||||
let button = UIButton(type: .custom)
|
||||
button.setImage(UIImage(named: "arrow_left_icon_01"), for: .normal)
|
||||
@ -28,12 +32,18 @@ class SPSearchViewController: SPViewController {
|
||||
///搜索首页
|
||||
private lazy var homeView: 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
|
||||
}()
|
||||
|
||||
///联想页面
|
||||
private lazy var associativeView: SPSearchAssociativeView = {
|
||||
let view = SPSearchAssociativeView()
|
||||
view.viewModel = self.viewModel
|
||||
view.isHidden = true
|
||||
return view
|
||||
}()
|
||||
@ -48,6 +58,8 @@ class SPSearchViewController: SPViewController {
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
searchInputView.textField.becomeFirstResponder()
|
||||
|
||||
_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
|
||||
} else {
|
||||
let string = NSMutableAttributedString(string: model?.name ?? "")
|
||||
string.color = .color8A899F()
|
||||
string.color = .colorEAF7FF()
|
||||
|
||||
if let range = model?.name?.ocString().range(of: searchText ?? "") {
|
||||
string.setColor(.colorFFFFFF(), range: range)
|
||||
if let range = model?.name?.lowercased().ocString().range(of: (searchText ?? "").lowercased()) {
|
||||
string.setColor(.colorFF4B5A(), range: range)
|
||||
}
|
||||
|
||||
titleLabel.attributedText = string
|
||||
@ -33,6 +33,8 @@ class SPSearchAssociativeCell: SPTableViewCell {
|
||||
|
||||
private lazy var iconImageView: UIImageView = {
|
||||
let imageView = UIImageView(image: UIImage(named: "play_icon_03"))
|
||||
imageView.setContentHuggingPriority(.required, for: .horizontal)
|
||||
imageView.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||
return imageView
|
||||
}()
|
||||
|
||||
@ -62,12 +64,12 @@ extension SPSearchAssociativeCell {
|
||||
|
||||
iconImageView.snp.makeConstraints { make in
|
||||
make.centerY.equalToSuperview()
|
||||
make.left.equalToSuperview().offset(15)
|
||||
make.left.equalToSuperview().offset(16)
|
||||
}
|
||||
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.centerY.equalToSuperview()
|
||||
make.left.equalToSuperview().offset(40)
|
||||
make.left.equalToSuperview().offset(36)
|
||||
make.right.lessThanOrEqualToSuperview().offset(-15)
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import UIKit
|
||||
|
||||
class SPSearchAssociativeView: UIView {
|
||||
|
||||
var viewModel: SPSearchViewModel?
|
||||
|
||||
private(set) lazy var searchText: String = ""
|
||||
|
||||
@ -18,8 +19,11 @@ class SPSearchAssociativeView: UIView {
|
||||
let tableView = SPTableView(frame: .zero, style: .plain)
|
||||
tableView.delegate = 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.keyboardDismissMode = .onDrag
|
||||
SPSearchAssociativeCell.registerCell(tableView: tableView)
|
||||
return tableView
|
||||
}()
|
||||
@ -71,6 +75,7 @@ extension SPSearchAssociativeView: UITableViewDelegate, UITableViewDataSource {
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
let model = self.dataArr[indexPath.row]
|
||||
self.viewModel?.addSearchHistory(text: self.searchText)
|
||||
|
||||
let vc = SPPlayerDetailViewController()
|
||||
vc.shortPlayId = model.short_play_id
|
||||
|
@ -10,5 +10,87 @@ import UIKit
|
||||
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 {
|
||||
|
||||
override var intrinsicContentSize: CGSize {
|
||||
return CGSize(width: kSPScreenWidth, height: 38)
|
||||
return CGSize(width: kSPScreenWidth, height: 36)
|
||||
}
|
||||
|
||||
var placeholder: String? {
|
||||
@ -31,7 +31,8 @@ class SPSearchInputView: UIView {
|
||||
|
||||
private(set) lazy var 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.textDidChange = { [weak self] text in
|
||||
self?.textDidChange?(text)
|
||||
@ -41,9 +42,12 @@ class SPSearchInputView: UIView {
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
layer.cornerRadius = 19
|
||||
layer.cornerRadius = 8
|
||||
layer.masksToBounds = true
|
||||
backgroundColor = .colorFFFFFF(alpha: 0.1)
|
||||
backgroundColor = .colorFFFFFF(alpha: 0.2)
|
||||
|
||||
setContentHuggingPriority(.required, for: .vertical)
|
||||
setContentCompressionResistancePriority(.required, for: .vertical)
|
||||
|
||||
_setupUI()
|
||||
|
||||
@ -62,13 +66,13 @@ extension SPSearchInputView {
|
||||
addSubview(textField)
|
||||
|
||||
iconImageView.snp.makeConstraints { make in
|
||||
make.left.equalToSuperview().offset(15)
|
||||
make.left.equalToSuperview().offset(16)
|
||||
make.centerY.equalToSuperview()
|
||||
}
|
||||
|
||||
textField.snp.makeConstraints { make in
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -14,4 +14,6 @@ class SPHomeViewModel: NSObject {
|
||||
///历史记录
|
||||
var playHistoryArr: [SPShortModel]?
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -9,6 +9,53 @@ import UIKit
|
||||
|
||||
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"
|
||||
},
|
||||
{
|
||||
"filename" : "nav_back@2x.png",
|
||||
"filename" : "Frame 70@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "Frame 70@3x.png",
|
||||
"idiom" : "universal",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"filename" : "play@2x.png",
|
||||
"filename" : "Frame 71@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "play@3x.png",
|
||||
"filename" : "Frame 71@3x.png",
|
||||
"idiom" : "universal",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"filename" : "搜索图标@2x.png",
|
||||
"filename" : "Frame 26@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "搜索图标@3x.png",
|
||||
"filename" : "Frame 26@3x.png",
|
||||
"idiom" : "universal",
|
||||
"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";
|
||||
"More for you!" = "More for you!";
|
||||
"More" = "More";
|
||||
"Historical search" = "Historical search";
|
||||
"Top Search" = "Top Search";
|
||||
|
||||
///视频详情标题
|
||||
"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])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|