搜索页面开发

This commit is contained in:
2025-04-22 17:30:04 +08:00
parent 092092e546
commit b30d9070d3
39 changed files with 936 additions and 21 deletions

View File

@ -9,3 +9,6 @@ import UIKit
/// ///
let kSPLoginTokenDefaultsKey = "kSPLoginTokenDefaultsKey" let kSPLoginTokenDefaultsKey = "kSPLoginTokenDefaultsKey"
///
let kSPHomeSearchHistoryDefaultsKey = "kSPHomeSearchHistoryDefaultsKey"

View File

@ -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)
}
} }

View File

@ -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)
}
}
} }

View File

@ -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) {

View File

@ -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()
} }

View 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)
}
}
}

View 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()
}
}
}
}

View 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)
}
}

View File

@ -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)
} }
} }

View File

@ -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

View File

@ -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)
}
} }

View File

@ -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)
} }
} }

View File

@ -14,4 +14,6 @@ class SPHomeViewModel: NSObject {
/// ///
var playHistoryArr: [SPShortModel]? var playHistoryArr: [SPShortModel]?
} }

View File

@ -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()
}
} }

View File

@ -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"
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 B

View 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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 632 B

View 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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -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"
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 860 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 895 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -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"
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 933 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1000 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

View 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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1011 KiB

View File

@ -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 %@ / %@";

View 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])
}
}
}