选集功能开发
@ -14,7 +14,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
|
||||
|
||||
self.appConfig()
|
||||
|
||||
SPLoginManager.manager.requestVisitorLogin(completer: nil)
|
||||
|
@ -26,3 +26,11 @@ extension String: SmartCodable {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension String {
|
||||
///获取文字Size
|
||||
func size(font: UIFont, size: CGSize = CGSize(width: CGFloat(MAXFLOAT), height: CGFloat(MAXFLOAT))) -> CGSize{
|
||||
let string: NSString = self as NSString
|
||||
return string.size(for: font, size: size, mode: .byWordWrapping)
|
||||
}
|
||||
}
|
||||
|
@ -63,5 +63,7 @@ extension UIColor {
|
||||
static func colorF56490(alpha: CGFloat = 1) -> UIColor {
|
||||
return color(hex: 0xF56490, alpha: alpha)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
@ -41,4 +41,37 @@ class SPVideoAPI: NSObject {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
///收藏短剧
|
||||
static func requestCollectShort(isCollect: Bool, shortPlayId: String, success: (() -> Void)?) {
|
||||
let path: String
|
||||
if isCollect {
|
||||
path = "/collect"
|
||||
} else {
|
||||
path = "/cancelCollect"
|
||||
}
|
||||
|
||||
var param = SPNetworkParameters(path: path)
|
||||
param.isLoding = true
|
||||
param.parameters = [
|
||||
"short_play_id" : shortPlayId
|
||||
]
|
||||
|
||||
SPNetwork.request(parameters: param) { (response: SPNetworkResponse<String>) in
|
||||
if response.code == SPNetworkCodeSucceed {
|
||||
success?()
|
||||
NotificationCenter.default.post(name: SPVideoAPI.updateShortCollectStateNotification, object: nil, userInfo: [
|
||||
"state" : isCollect,
|
||||
"id" : shortPlayId,
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SPVideoAPI {
|
||||
///更新短剧关注状态 [ "state" : isCollect, "id" : shortPlayId,]
|
||||
@objc static let updateShortCollectStateNotification = NSNotification.Name(rawValue: "SPVideoAPI.updateShortCollectStateNotification")
|
||||
|
||||
}
|
||||
|
101
ShortPlay/Base/Networking/Base/CryptorService.swift
Normal file
@ -0,0 +1,101 @@
|
||||
//// CryptorService.swift
|
||||
// QJDrama
|
||||
//
|
||||
// Created by Yao on 2025/4/16
|
||||
//
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class CryptorService {
|
||||
// 定义常量
|
||||
static let EN_STR_TAG: String = "$" // 替换为实际的加密标记
|
||||
|
||||
// 解密字符串
|
||||
static func decrypt(data: String) -> String {
|
||||
guard data.hasPrefix(EN_STR_TAG) else {
|
||||
// fatalError("Invalid encoded string")
|
||||
return data
|
||||
}
|
||||
|
||||
let decryptedData = deStrBytes(data: data)
|
||||
return String(data: decryptedData, encoding: .utf8) ?? ""
|
||||
}
|
||||
|
||||
// 从十六进制字符串解密字节
|
||||
static func deStrBytes(data: String) -> Data {
|
||||
|
||||
let hexData = String(data.dropFirst())
|
||||
var bytes = Data()
|
||||
|
||||
var index = hexData.startIndex
|
||||
while index < hexData.endIndex {
|
||||
let nextIndex = hexData.index(index, offsetBy: 2, limitedBy: hexData.endIndex) ?? hexData.endIndex
|
||||
let byteString = String(hexData[index..<nextIndex])
|
||||
|
||||
if let byte = UInt8(byteString, radix: 16) {
|
||||
bytes.append(byte)
|
||||
}
|
||||
|
||||
index = nextIndex
|
||||
}
|
||||
|
||||
return de(data: bytes)
|
||||
}
|
||||
|
||||
// 解密数据
|
||||
static func de(data: Data) -> Data {
|
||||
guard !data.isEmpty else {
|
||||
return data
|
||||
}
|
||||
|
||||
let saltLen = Int(data[data.startIndex])
|
||||
guard data.count >= 1 + saltLen else {
|
||||
return data
|
||||
}
|
||||
|
||||
let salt = data.subdata(in: 1..<1+saltLen)
|
||||
let encryptedData = data.subdata(in: 1+saltLen..<data.count)
|
||||
|
||||
return deWithSalt(data: encryptedData, salt: salt)
|
||||
}
|
||||
|
||||
// 使用盐值解密数据
|
||||
static func deWithSalt(data: Data, salt: Data) -> Data {
|
||||
let decryptedData = cxEd(data: data)
|
||||
return removeSalt(data: decryptedData, salt: salt)
|
||||
}
|
||||
|
||||
// 加密/解密数据(按位取反)
|
||||
static func cxEd(data: Data) -> Data {
|
||||
return Data(data.map { $0 ^ 0xFF })
|
||||
}
|
||||
|
||||
// 从数据中移除盐值
|
||||
static func removeSalt(data: Data, salt: Data) -> Data {
|
||||
guard !salt.isEmpty else {
|
||||
return data
|
||||
}
|
||||
|
||||
var result = Data()
|
||||
let saltBytes = [UInt8](salt)
|
||||
let saltCount = saltBytes.count
|
||||
|
||||
for (index, byte) in data.enumerated() {
|
||||
let saltByte = saltBytes[index % saltCount]
|
||||
let decryptedByte = calRemoveSalt(v: byte, s: saltByte)
|
||||
result.append(decryptedByte)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// 计算移除盐值后的字节
|
||||
static func calRemoveSalt(v: UInt8, s: UInt8) -> UInt8 {
|
||||
if v >= s {
|
||||
return v - s
|
||||
} else {
|
||||
return UInt8(0xFF) - (s - v) + 1
|
||||
}
|
||||
}
|
||||
}
|
@ -34,11 +34,14 @@ class SPNetwork: NSObject {
|
||||
while loding {
|
||||
if !SPLoginManager.manager.isRefreshingToken {
|
||||
loding = false
|
||||
spLog(message: "======等待结束")
|
||||
} else {
|
||||
spLog(message: "======等待中")
|
||||
}
|
||||
RunLoop.current.run(mode: .default, before: Date.distantFuture)
|
||||
}
|
||||
}
|
||||
|
||||
spLog(message: "======开始请求")
|
||||
_request(parameters: parameters, completion: completion)
|
||||
}
|
||||
|
||||
@ -140,11 +143,11 @@ class SPNetwork: NSObject {
|
||||
var response: SPNetworkResponse<T>?
|
||||
|
||||
let time = Date().timeIntervalSince1970
|
||||
if let decrypted = SPCryptService.decrypt(data) {
|
||||
spLog(message: decrypted)
|
||||
response = SPNetworkResponse<T>.deserialize(from: decrypted)
|
||||
response?.rawData = decrypted
|
||||
}
|
||||
let decrypted = CryptorService.decrypt(data: data)
|
||||
spLog(message: decrypted)
|
||||
response = SPNetworkResponse<T>.deserialize(from: decrypted)
|
||||
response?.rawData = decrypted
|
||||
|
||||
spLog(message: Date().timeIntervalSince1970 - time)
|
||||
|
||||
if let response = response {
|
||||
|
@ -7,6 +7,18 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
/*
|
||||
https://api-moviatv.moviatv.com/93f03506/
|
||||
|
||||
https://api-mireotv.mireotv.com/4da6fd4c/
|
||||
|
||||
https://api-vibeoshort.vibeoshort.com/bf86d973/
|
||||
|
||||
https://api-viontv.viontv.com/b7afef99/
|
||||
|
||||
https://api-zyreotv.zyreotv.com/7834f11d/
|
||||
*/
|
||||
|
||||
#if DEBUG
|
||||
let SPBaseURL = "https://test1-api.guyantv.com"
|
||||
let SPWebBaseURL = "https://www.guyantv.com"
|
||||
|
@ -46,7 +46,7 @@ extension SPForYouViewController: SPPlayerListViewControllerDataSource {
|
||||
|
||||
if let cell = oldCell as? SPPlayerListCell {
|
||||
if let model = dataArr[indexPath.row] as? SPShortModel {
|
||||
cell.model = model
|
||||
cell.shortModel = model
|
||||
cell.videoInfo = model.video_info
|
||||
}
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ extension SPHomeHeaderView: ZKCycleScrollViewDelegate, ZKCycleScrollViewDataSour
|
||||
func cycleScrollView(_ cycleScrollView: ZKCycleScrollView, didSelectItemAt index: Int) {
|
||||
let model = moduleModel?.bannerData?[index]
|
||||
|
||||
let vc = SPTVPlayerListViewController()
|
||||
let vc = SPPlayerDetailViewController()
|
||||
vc.shortPlayId = model?.short_play_id
|
||||
self.viewController?.navigationController?.pushViewController(vc, animated: true)
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ extension SPHomeHotView: UICollectionViewDelegate, UICollectionViewDataSource {
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
let model = self.dataArr?[indexPath.row]
|
||||
|
||||
let vc = SPTVPlayerListViewController()
|
||||
let vc = SPPlayerDetailViewController()
|
||||
vc.shortPlayId = model?.short_play_id
|
||||
self.viewController?.navigationController?.pushViewController(vc, animated: true)
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ extension SPHomeTrendingView: UICollectionViewDataSource, UICollectionViewDelega
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
let model = self.dataArr?[indexPath.row]
|
||||
|
||||
let vc = SPTVPlayerListViewController()
|
||||
let vc = SPPlayerDetailViewController()
|
||||
vc.shortPlayId = model?.short_play_id
|
||||
self.viewController?.navigationController?.pushViewController(vc, animated: true)
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
//
|
||||
// SPTVPlayerListViewController.swift
|
||||
// SPPlayerDetailViewController.swift
|
||||
// ShortPlay
|
||||
//
|
||||
// Created by 曾觉新 on 2025/4/10.
|
||||
@ -7,10 +7,10 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPTVPlayerListViewController: SPPlayerListViewController {
|
||||
class SPPlayerDetailViewController: SPPlayerListViewController {
|
||||
|
||||
override var PlayerCellClass: SPPlayerListCell.Type {
|
||||
return SPTVPlayerListCell.self
|
||||
return SPPlayerDetailCell.self
|
||||
}
|
||||
|
||||
override var contentSize: CGSize {
|
||||
@ -23,12 +23,17 @@ class SPTVPlayerListViewController: SPPlayerListViewController {
|
||||
|
||||
private var detailModel: SPVideoDetailModel?
|
||||
|
||||
private weak var episodeView: SPEpisodeView?
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
self.autoNextEpisode = true
|
||||
self.dataSource = self
|
||||
self.delegate = self
|
||||
|
||||
requestDetailData()
|
||||
|
||||
_addAction()
|
||||
}
|
||||
|
||||
|
||||
@ -49,11 +54,33 @@ class SPTVPlayerListViewController: SPPlayerListViewController {
|
||||
|
||||
}
|
||||
|
||||
extension SPPlayerDetailViewController {
|
||||
|
||||
private func _addAction() {
|
||||
self.viewModel.handleEpisode = { [weak self] in
|
||||
self?.onEpisode()
|
||||
}
|
||||
}
|
||||
|
||||
private func onEpisode() {
|
||||
let view = SPEpisodeView()
|
||||
view.dataArr = detailModel?.episodeList ?? []
|
||||
view.shortModel = detailModel?.shortPlayInfo
|
||||
view.currentIndex = self.currentIndexPath.row
|
||||
view.didSelectedIndex = { [weak self] (index) in
|
||||
self?.scrollToItem(indexPath: IndexPath(row: index, section: 0))
|
||||
}
|
||||
view.present(in: nil)
|
||||
self.episodeView = view
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//MARK: -------------- SPPlayerListViewControllerDataSource --------------
|
||||
extension SPTVPlayerListViewController: SPPlayerListViewControllerDataSource {
|
||||
extension SPPlayerDetailViewController: SPPlayerListViewControllerDataSource, SPPlayerListViewControllerDelegate {
|
||||
func sp_playerListViewController(_ viewController: SPPlayerListViewController, _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath, oldCell: UICollectionViewCell) -> UICollectionViewCell {
|
||||
if let cell = oldCell as? SPPlayerListCell {
|
||||
cell.model = detailModel
|
||||
if let cell = oldCell as? SPPlayerDetailCell {
|
||||
cell.shortModel = detailModel?.shortPlayInfo
|
||||
cell.videoInfo = detailModel?.episodeList?[indexPath.row]
|
||||
cell.isLoop = false
|
||||
}
|
||||
@ -63,10 +90,14 @@ extension SPTVPlayerListViewController: SPPlayerListViewControllerDataSource {
|
||||
func sp_playerListViewController(_ viewController: SPPlayerListViewController, _ collectionView: UICollectionView, numberOfItemsInSection section: Int, oldNumber: Int) -> Int {
|
||||
return detailModel?.episodeList?.count ?? 0
|
||||
}
|
||||
|
||||
func sp_playerListViewController(_ viewController: SPPlayerListViewController, didChangeIndexPathForVisible indexPath: IndexPath) {
|
||||
self.episodeView?.currentIndex = indexPath.row
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension SPTVPlayerListViewController {
|
||||
extension SPPlayerDetailViewController {
|
||||
|
||||
private func requestDetailData() {
|
||||
guard let shortPlayId = self.shortPlayId else { return }
|
@ -19,6 +19,8 @@ import UIKit
|
||||
///向上加载更多数据
|
||||
@objc optional func sp_playerViewControllerLoadUpMoreData(playerViewController: SPPlayerListViewController)
|
||||
|
||||
///当前展示的发生变化
|
||||
@objc optional func sp_playerListViewController(_ viewController: SPPlayerListViewController, didChangeIndexPathForVisible indexPath: IndexPath)
|
||||
///新页面展示完成
|
||||
// @objc optional func yd_playerViewController(playerListViewController: BCListPlayerViewController, didShowPlayerPage playerViewController: YDBasePlayerViewController)
|
||||
}
|
||||
@ -31,6 +33,7 @@ import UIKit
|
||||
func sp_playerListViewController(_ viewController: SPPlayerListViewController, _ collectionView: UICollectionView, numberOfItemsInSection section: Int, oldNumber: Int) -> Int
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
class SPPlayerListViewController: SPViewController {
|
||||
@ -153,6 +156,10 @@ class SPPlayerListViewController: SPViewController {
|
||||
func getDataCount() -> Int {
|
||||
return self.collectionView(self.collectionView, numberOfItemsInSection: 0)
|
||||
}
|
||||
|
||||
func scrollToItem(indexPath: IndexPath) {
|
||||
self.collectionView.scrollToItem(at: indexPath, at: .top, animated: true);
|
||||
}
|
||||
}
|
||||
|
||||
extension SPPlayerListViewController {
|
||||
@ -175,6 +182,7 @@ extension SPPlayerListViewController {
|
||||
self.viewModel.handlePlayFinish = { [weak self] in
|
||||
self?.currentPlayFinish()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -227,6 +235,8 @@ extension SPPlayerListViewController {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//MARK: -------------- UICollectionViewDelegate & UICollectionViewDataSource --------------
|
||||
@ -251,6 +261,7 @@ extension SPPlayerListViewController: UICollectionViewDelegate, UICollectionView
|
||||
if self.viewModel.currentPlayer == nil, indexPath == currentIndexPath, let playerProtocol = cell as? SPPlayerProtocol {
|
||||
self.currentIndexPath = indexPath
|
||||
self.viewModel.currentPlayer = playerProtocol
|
||||
didChangeIndexPathForVisible()
|
||||
}
|
||||
|
||||
return cell
|
||||
@ -293,6 +304,7 @@ extension SPPlayerListViewController: UICollectionViewDelegate, UICollectionView
|
||||
guard let currentPlayer = self.collectionView.cellForItem(at: indexPath) as? SPPlayerProtocol else { return }
|
||||
self.viewModel.currentPlayer = currentPlayer
|
||||
// currentCell = self.collectionView.cellForItem(at: indexPath) as? BCListPlayerCell
|
||||
didChangeIndexPathForVisible()
|
||||
self.play()
|
||||
}
|
||||
|
||||
@ -329,4 +341,8 @@ extension SPPlayerListViewController {
|
||||
// }
|
||||
self.delegate?.sp_playerViewControllerLoadUpMoreData?(playerViewController: self)
|
||||
}
|
||||
|
||||
private func didChangeIndexPathForVisible() {
|
||||
self.delegate?.sp_playerListViewController?(self, didChangeIndexPathForVisible: self.currentIndexPath)
|
||||
}
|
||||
}
|
||||
|
70
ShortPlay/Class/Player/View/SPEpisodeCell.swift
Normal file
@ -0,0 +1,70 @@
|
||||
//
|
||||
// SPEpisodeCell.swift
|
||||
// ShortPlay
|
||||
//
|
||||
// Created by 曾觉新 on 2025/4/16.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPEpisodeCell: SPCollectionViewCell {
|
||||
|
||||
|
||||
var videoInfoModel: SPVideoInfoModel? {
|
||||
didSet {
|
||||
textLabel.text = "\(videoInfoModel?.episode ?? 0)"
|
||||
}
|
||||
}
|
||||
|
||||
var sp_isSelected: Bool = false {
|
||||
didSet {
|
||||
textLabel.isHidden = sp_isSelected
|
||||
playImageView.isHidden = !sp_isSelected
|
||||
}
|
||||
}
|
||||
|
||||
private lazy var textLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .fontLight(ofSize: 14)
|
||||
label.textColor = .colorD2D2D2()
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var playImageView: UIImageView = {
|
||||
let imageView = UIImageView(image: UIImage(named: "play_icon_01"))
|
||||
return imageView
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
contentView.backgroundColor = .colorFFFFFF(alpha: 0.14)
|
||||
contentView.layer.cornerRadius = 7
|
||||
contentView.layer.masksToBounds = true
|
||||
|
||||
|
||||
_setupUI()
|
||||
}
|
||||
|
||||
@MainActor required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
extension SPEpisodeCell {
|
||||
|
||||
private func _setupUI() {
|
||||
contentView.addSubview(textLabel)
|
||||
contentView.addSubview(playImageView)
|
||||
|
||||
textLabel.snp.makeConstraints { make in
|
||||
make.center.equalToSuperview()
|
||||
}
|
||||
|
||||
playImageView.snp.makeConstraints { make in
|
||||
make.center.equalToSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
153
ShortPlay/Class/Player/View/SPEpisodeView.swift
Normal file
@ -0,0 +1,153 @@
|
||||
//
|
||||
// SPEpisodeView.swift
|
||||
// ShortPlay
|
||||
//
|
||||
// Created by 曾觉新 on 2025/4/16.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPEpisodeView: HWPanModalContentView {
|
||||
|
||||
var currentIndex: Int = 0 {
|
||||
didSet {
|
||||
self.collectionView.reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
var shortModel: SPShortModel? {
|
||||
didSet {
|
||||
coverImageView.sp_setImage(url: shortModel?.image_url)
|
||||
}
|
||||
}
|
||||
|
||||
var dataArr: [SPVideoInfoModel] = [] {
|
||||
didSet {
|
||||
self.collectionView.reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
var didSelectedIndex: ((_ index: Int) -> Void)?
|
||||
|
||||
//MARK: UI属性
|
||||
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
|
||||
let itemWidth = floor((kSPScreenWidth - 10 * 4 - 30) / 5)
|
||||
|
||||
let layout = UICollectionViewFlowLayout()
|
||||
layout.itemSize = .init(width: itemWidth, height: 50)
|
||||
layout.minimumLineSpacing = 10
|
||||
layout.minimumInteritemSpacing = 10
|
||||
layout.sectionInset = .init(top: 0, left: 15, bottom: 0, right: 15)
|
||||
return layout
|
||||
}()
|
||||
|
||||
private lazy var collectionView: SPCollectionView = {
|
||||
let collectionView = SPCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
|
||||
collectionView.delegate = self
|
||||
collectionView.dataSource = self
|
||||
SPEpisodeCell.registerCell(collectionView: collectionView)
|
||||
return collectionView
|
||||
}()
|
||||
|
||||
private lazy var indicatorView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .colorFFFFFF()
|
||||
view.layer.cornerRadius = 2
|
||||
view.layer.masksToBounds = true
|
||||
return view
|
||||
}()
|
||||
|
||||
private lazy var coverImageView: SPImageView = {
|
||||
let imageView = SPImageView()
|
||||
imageView.layer.cornerRadius = 6
|
||||
imageView.layer.masksToBounds = true
|
||||
return imageView
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
_setupUI()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
|
||||
//MARK: HWPanModalPresentable
|
||||
override func panScrollable() -> UIScrollView? {
|
||||
return collectionView
|
||||
}
|
||||
|
||||
override func longFormHeight() -> PanModalHeight {
|
||||
return PanModalHeightMake(.content, kSPScreenHeight * (2 / 3))
|
||||
}
|
||||
|
||||
override func showDragIndicator() -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
override func backgroundConfig() -> HWBackgroundConfig {
|
||||
let config = HWBackgroundConfig()
|
||||
config.backgroundAlpha = 0.4
|
||||
return config
|
||||
}
|
||||
|
||||
override func present(in view: UIView?) {
|
||||
super.present(in: view)
|
||||
self.hw_contentView.addEffectView(style: .dark)
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension SPEpisodeView {
|
||||
|
||||
private func _setupUI() {
|
||||
addSubview(indicatorView)
|
||||
addSubview(coverImageView)
|
||||
addSubview(self.collectionView)
|
||||
|
||||
self.indicatorView.snp.makeConstraints { make in
|
||||
make.centerX.equalToSuperview()
|
||||
make.top.equalToSuperview().offset(15)
|
||||
make.width.equalTo(30)
|
||||
make.height.equalTo(4)
|
||||
}
|
||||
|
||||
self.coverImageView.snp.makeConstraints { make in
|
||||
make.left.equalToSuperview().offset(15)
|
||||
make.top.equalToSuperview().offset(39)
|
||||
make.width.equalTo(55)
|
||||
make.height.equalTo(74)
|
||||
}
|
||||
|
||||
self.collectionView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//MARK: -------------- UICollectionViewDelegate & UICollectionViewDataSource --------------
|
||||
extension SPEpisodeView: UICollectionViewDelegate, UICollectionViewDataSource {
|
||||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
let cell = SPEpisodeCell.dequeueReusableCell(collectionView: collectionView, indexPath: indexPath)
|
||||
cell.videoInfoModel = self.dataArr[indexPath.row]
|
||||
cell.sp_isSelected = indexPath.row == currentIndex
|
||||
return cell
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return self.dataArr.count
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
guard indexPath.row != currentIndex else { return }
|
||||
self.didSelectedIndex?(indexPath.row)
|
||||
self.dismiss(animated: true) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -15,15 +15,20 @@ class SPPlayerControlView: UIView {
|
||||
}
|
||||
}
|
||||
|
||||
var model: Any? {
|
||||
|
||||
var shortModel: SPShortModel? {
|
||||
didSet {
|
||||
updateCollectButtonState()
|
||||
}
|
||||
}
|
||||
|
||||
var videoInfo: SPVideoInfoModel? {
|
||||
didSet {
|
||||
guard let model = model as? SPShortModel else { return }
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///滑动进度条
|
||||
var panProgressFinishBlock: ((_ progress: CGFloat) -> Void)?
|
||||
|
||||
@ -39,7 +44,8 @@ class SPPlayerControlView: UIView {
|
||||
updatePlayIconState()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//MARK: UI属性
|
||||
private(set) lazy var progressView: SPPlayerProgressView = {
|
||||
let view = SPPlayerProgressView()
|
||||
view.panStart = { [weak self] in
|
||||
@ -65,17 +71,36 @@ class SPPlayerControlView: UIView {
|
||||
return imageView
|
||||
}()
|
||||
|
||||
///右边功能区
|
||||
private(set) lazy var rightFeatureView: UIStackView = {
|
||||
let view = UIStackView(arrangedSubviews: [collectButton])
|
||||
view.axis = .vertical
|
||||
view.spacing = 25
|
||||
return view
|
||||
}()
|
||||
|
||||
///收藏按钮
|
||||
private lazy var collectButton: UIButton = {
|
||||
let button = createFeatureButton(title: "Save".localized, selectedTitle: "Added".localized, image: UIImage(named: "collect_icon_01"), selectedImage: UIImage(named: "collect_icon_01_selected"))
|
||||
button.addTarget(self, action: #selector(handleCollectButton), for: .touchUpInside)
|
||||
return button
|
||||
}()
|
||||
|
||||
deinit {
|
||||
viewModel?.removeObserver(self, forKeyPath: "isPlaying")
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(updateShortCollectStateNotification), name: SPVideoAPI.updateShortCollectStateNotification, object: nil)
|
||||
|
||||
let tap = UITapGestureRecognizer(target: self, action: #selector(hadlePlayAndOrPaused))
|
||||
self.addGestureRecognizer(tap)
|
||||
|
||||
|
||||
|
||||
sp_setupUI()
|
||||
}
|
||||
|
||||
@ -89,6 +114,23 @@ class SPPlayerControlView: UIView {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
|
||||
func createFeatureButton(title: String?, selectedTitle: String? = nil, image: UIImage?, selectedImage: UIImage? = nil) -> UIButton {
|
||||
let button = JXButton(type: .custom)
|
||||
button.titleDirection = .down
|
||||
button.setImage(image, for: .normal)
|
||||
button.setImage(selectedImage, for: .selected)
|
||||
button.setImage(selectedImage, for: [.selected, .highlighted])
|
||||
button.setTitle(title, for: .normal);
|
||||
button.setTitle(selectedTitle, for: .selected);
|
||||
button.setTitle(selectedTitle, for: [.selected, .highlighted])
|
||||
button.setTitleColor(.colorFFFFFF(alpha: 0.9), for: .normal)
|
||||
button.setTitleColor(.colorF564B6(), for: .selected)
|
||||
button.jx_font = .fontLight(ofSize: 11);
|
||||
return button
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension SPPlayerControlView {
|
||||
@ -96,6 +138,7 @@ extension SPPlayerControlView {
|
||||
private func sp_setupUI() {
|
||||
addSubview(progressView)
|
||||
addSubview(playImageView)
|
||||
addSubview(rightFeatureView)
|
||||
|
||||
progressView.snp.makeConstraints { make in
|
||||
make.left.equalToSuperview().offset(10)
|
||||
@ -108,6 +151,11 @@ extension SPPlayerControlView {
|
||||
make.center.equalToSuperview()
|
||||
make.width.height.equalTo(100)
|
||||
}
|
||||
|
||||
rightFeatureView.snp.makeConstraints { make in
|
||||
make.right.equalToSuperview().offset(-15)
|
||||
make.bottom.equalToSuperview().offset(-200)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,7 +180,29 @@ extension SPPlayerControlView {
|
||||
// SPAPPTool.topViewController()?.navigationController?.pushViewController(vc, animated: true)
|
||||
}
|
||||
|
||||
@objc private func handleCollectButton() {
|
||||
guard let shortPlayId = self.videoInfo?.short_play_id else { return }
|
||||
|
||||
let isCollect = !(self.shortModel?.is_collect ?? false)
|
||||
|
||||
SPVideoAPI.requestCollectShort(isCollect: isCollect, shortPlayId: shortPlayId) {
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func updateShortCollectStateNotification(sender: Notification) {
|
||||
guard let userInfo = sender.userInfo else { return }
|
||||
guard let shortPlayId = userInfo["id"] as? String else { return }
|
||||
guard let isCollect = userInfo["state"] as? Bool else { return }
|
||||
guard shortPlayId == self.videoInfo?.short_play_id else { return }
|
||||
|
||||
self.shortModel?.is_collect = isCollect;
|
||||
updateCollectButtonState()
|
||||
|
||||
}
|
||||
|
||||
private func updateCollectButtonState() {
|
||||
self.collectButton.isSelected = self.shortModel?.is_collect ?? false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
26
ShortPlay/Class/Player/View/SPPlayerDetailCell.swift
Normal file
@ -0,0 +1,26 @@
|
||||
//
|
||||
// SPPlayerDetailCell.swift
|
||||
// ShortPlay
|
||||
//
|
||||
// Created by 曾觉新 on 2025/4/10.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPPlayerDetailCell: SPPlayerListCell {
|
||||
|
||||
override var PlayerControlViewClass: SPPlayerControlView.Type {
|
||||
return SPPlayerDetailControlView.self
|
||||
}
|
||||
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
@MainActor required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
|
||||
}
|
44
ShortPlay/Class/Player/View/SPPlayerDetailControlView.swift
Normal file
@ -0,0 +1,44 @@
|
||||
//
|
||||
// SPPlayerDetailControlView.swift
|
||||
// ShortPlay
|
||||
//
|
||||
// Created by 曾觉新 on 2025/4/16.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPPlayerDetailControlView: SPPlayerControlView {
|
||||
|
||||
|
||||
private lazy var episodeButton: UIButton = {
|
||||
let button = createFeatureButton(title: "Episodes".localized, image: UIImage(named: "episodes_icon_01"))
|
||||
button.addTarget(self, action: #selector(handleEpisodeButton), for: .touchUpInside)
|
||||
return button
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
_setupUI()
|
||||
}
|
||||
|
||||
@MainActor required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SPPlayerDetailControlView {
|
||||
|
||||
private func _setupUI() {
|
||||
self.rightFeatureView.addArrangedSubview(episodeButton)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SPPlayerDetailControlView {
|
||||
@objc private func handleEpisodeButton() {
|
||||
self.viewModel?.handleEpisode?()
|
||||
}
|
||||
}
|
@ -9,6 +9,10 @@ import UIKit
|
||||
|
||||
class SPPlayerListCell: SPCollectionViewCell, SPPlayerProtocol {
|
||||
|
||||
var PlayerControlViewClass: SPPlayerControlView.Type {
|
||||
return SPPlayerControlView.self
|
||||
}
|
||||
|
||||
weak var viewModel: SPPlayerListViewModel? {
|
||||
didSet {
|
||||
controlView.viewModel = viewModel
|
||||
@ -39,7 +43,7 @@ class SPPlayerListCell: SPCollectionViewCell, SPPlayerProtocol {
|
||||
}()
|
||||
|
||||
private lazy var controlView: SPPlayerControlView = {
|
||||
let view = SPPlayerControlView()
|
||||
let view = PlayerControlViewClass.init()
|
||||
view.panProgressFinishBlock = { [weak self] progress in
|
||||
guard let self = self else { return }
|
||||
let duration = CGFloat(self.player.duration)
|
||||
@ -65,19 +69,30 @@ class SPPlayerListCell: SPCollectionViewCell, SPPlayerProtocol {
|
||||
self.controlView.progress = 0
|
||||
self.coverImageView.isHidden = false
|
||||
|
||||
if let model = model as? SPShortModel {
|
||||
self.controlView.model = model
|
||||
coverImageView.sp_setImage(url: model.image_url)
|
||||
} else if let model = model as? SPVideoDetailModel {
|
||||
self.controlView.model = model.shortPlayInfo
|
||||
coverImageView.sp_setImage(url: model.shortPlayInfo?.image_url)
|
||||
}
|
||||
// if let model = model as? SPShortModel {
|
||||
// self.controlView.model = model
|
||||
// coverImageView.sp_setImage(url: model.image_url)
|
||||
// } else if let model = model as? SPVideoDetailModel {
|
||||
// self.controlView.model = model.shortPlayInfo
|
||||
// coverImageView.sp_setImage(url: model.shortPlayInfo?.image_url)
|
||||
// }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var shortModel: SPShortModel? {
|
||||
didSet {
|
||||
self.controlView.shortModel = shortModel
|
||||
coverImageView.sp_setImage(url: shortModel?.image_url)
|
||||
}
|
||||
}
|
||||
|
||||
var videoInfo: SPVideoInfoModel? {
|
||||
didSet {
|
||||
self.controlView.progress = 0
|
||||
self.coverImageView.isHidden = false
|
||||
self.controlView.videoInfo = videoInfo
|
||||
|
||||
player.setPlayUrl(url: videoInfo?.video_url ?? "")
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +0,0 @@
|
||||
//
|
||||
// SPTVPlayerListCell.swift
|
||||
// ShortPlay
|
||||
//
|
||||
// Created by 曾觉新 on 2025/4/10.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPTVPlayerListCell: SPPlayerListCell {
|
||||
|
||||
}
|
@ -41,6 +41,7 @@ class SPPlayerListViewModel: NSObject {
|
||||
var handlePauseOrPlay: (() -> Void)?
|
||||
///播放完成
|
||||
var handlePlayFinish: (() -> Void)?
|
||||
|
||||
///选集
|
||||
var handleEpisode: (() -> Void)?
|
||||
|
||||
}
|
||||
|
22
ShortPlay/Source/Assets.xcassets/icon/collect_icon_01.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
ShortPlay/Source/Assets.xcassets/icon/collect_icon_01.imageset/收藏@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
ShortPlay/Source/Assets.xcassets/icon/collect_icon_01.imageset/收藏@3x.png
vendored
Normal file
After Width: | Height: | Size: 2.3 KiB |
22
ShortPlay/Source/Assets.xcassets/icon/collect_icon_01_selected.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
ShortPlay/Source/Assets.xcassets/icon/collect_icon_01_selected.imageset/收藏@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
ShortPlay/Source/Assets.xcassets/icon/collect_icon_01_selected.imageset/收藏@3x.png
vendored
Normal file
After Width: | Height: | Size: 3.3 KiB |
22
ShortPlay/Source/Assets.xcassets/icon/episodes_icon_01.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Episodes@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "Episodes@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
ShortPlay/Source/Assets.xcassets/icon/episodes_icon_01.imageset/Episodes@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
ShortPlay/Source/Assets.xcassets/icon/episodes_icon_01.imageset/Episodes@3x.png
vendored
Normal file
After Width: | Height: | Size: 3.3 KiB |
22
ShortPlay/Source/Assets.xcassets/icon/play_icon_01.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "play1@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "play1@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
ShortPlay/Source/Assets.xcassets/icon/play_icon_01.imageset/play1@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
ShortPlay/Source/Assets.xcassets/icon/play_icon_01.imageset/play1@3x.png
vendored
Normal file
After Width: | Height: | Size: 2.5 KiB |
@ -11,3 +11,4 @@
|
||||
#import <Toast.h>
|
||||
#import "NSUserDefaults+JXAdd.h"
|
||||
#import <KTVHTTPCache.h>
|
||||
#import <HWPanModal/HWPanModal.h>
|
||||
|
@ -17,3 +17,6 @@
|
||||
"Trending Now" = "Trending Now";
|
||||
"Editor's Hotlist" = "Editor's Hotlist";
|
||||
"Shorts for You" = "Shorts for You";
|
||||
"Episodes" = "Episodes";
|
||||
"Save" = "Save";
|
||||
"Added" = "Added";
|
||||
|
218
ShortPlay/Thirdparty/JXButton/JXButton.swift
vendored
Normal file
@ -0,0 +1,218 @@
|
||||
//
|
||||
// JXButton.swift
|
||||
// BoJia
|
||||
//
|
||||
// Created by 火山传媒 on 2024/5/31.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class JXButton: UIButton {
|
||||
lazy var jx_font: UIFont? = self.titleLabel?.font {
|
||||
didSet {
|
||||
self.titleLabel?.font = jx_font
|
||||
}
|
||||
}
|
||||
|
||||
var maxTitleWidth: CGFloat = 0
|
||||
var titleDirection: UITextLayoutDirection?
|
||||
///左右边距
|
||||
var leftAnyRightmargin: CGFloat = 0
|
||||
|
||||
///文字与图片的间距
|
||||
var space: CGFloat = 0
|
||||
|
||||
private var imageRect: CGRect = .zero
|
||||
private var titleRect: CGRect = .zero
|
||||
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
}
|
||||
|
||||
override var intrinsicContentSize: CGSize {
|
||||
|
||||
var width: CGFloat = 0
|
||||
var height: CGFloat = 0
|
||||
switch titleDirection {
|
||||
case .left:
|
||||
width = imageRect.width + titleRect.width + space
|
||||
if imageRect.height > titleRect.height {
|
||||
height = imageRect.height
|
||||
} else {
|
||||
height = titleRect.height
|
||||
}
|
||||
|
||||
case .up:
|
||||
if imageRect.width > titleRect.width {
|
||||
width = imageRect.width
|
||||
} else {
|
||||
width = titleRect.width
|
||||
}
|
||||
height = titleRect.height + imageRect.height + space
|
||||
|
||||
case .down:
|
||||
if imageRect.width > titleRect.width {
|
||||
width = imageRect.width
|
||||
} else {
|
||||
width = titleRect.width
|
||||
}
|
||||
height = titleRect.height + imageRect.height + space
|
||||
|
||||
default:
|
||||
width = imageRect.width + titleRect.width + space
|
||||
if imageRect.height > titleRect.height {
|
||||
height = imageRect.height
|
||||
} else {
|
||||
height = titleRect.height
|
||||
}
|
||||
}
|
||||
|
||||
let size = CGSize(width: width + leftAnyRightmargin * 2, height: height)
|
||||
return size
|
||||
}
|
||||
|
||||
override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
|
||||
let imageSize = currentImage?.size ?? .zero
|
||||
let textSize = self._textSize()
|
||||
let contentWidth = self.frame.size.width
|
||||
let contentHeight = self.frame.size.height
|
||||
|
||||
var x: CGFloat = 0
|
||||
var y: CGFloat = 0
|
||||
var width: CGFloat = 0
|
||||
var height: CGFloat = 0
|
||||
|
||||
switch titleDirection {
|
||||
case .left:
|
||||
x = (contentWidth - space) / 2 - imageSize.width / 2 + textSize.width / 2 + space
|
||||
y = contentHeight / 2 - imageSize.height / 2
|
||||
width = imageSize.width
|
||||
height = imageSize.height
|
||||
|
||||
case .up:
|
||||
x = contentWidth / 2 - imageSize.width / 2
|
||||
y = (contentHeight - space) / 2 - imageSize.height / 2 + textSize.height / 2 + space
|
||||
width = imageSize.width
|
||||
height = imageSize.height
|
||||
|
||||
case .down:
|
||||
x = contentWidth / 2 - imageSize.width / 2
|
||||
y = (contentHeight - space) / 2 - imageSize.height / 2 - textSize.height / 2
|
||||
width = imageSize.width
|
||||
height = imageSize.height
|
||||
|
||||
default:
|
||||
x = (contentWidth - space) / 2 - imageSize.width / 2 - textSize.width / 2
|
||||
y = contentHeight / 2 - imageSize.height / 2
|
||||
width = imageSize.width
|
||||
height = imageSize.height
|
||||
}
|
||||
self.imageRect = CGRect(x: x, y: y, width: width, height: height)
|
||||
|
||||
return self.imageRect
|
||||
}
|
||||
|
||||
|
||||
override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
|
||||
let imageSize = currentImage?.size ?? .zero
|
||||
let textSize = self._textSize()
|
||||
let contentWidth = self.frame.size.width
|
||||
let contentHeight = self.frame.size.height
|
||||
|
||||
var x: CGFloat = 0
|
||||
var y: CGFloat = 0
|
||||
var width: CGFloat = 0
|
||||
var height: CGFloat = 0
|
||||
|
||||
switch titleDirection {
|
||||
case .left:
|
||||
x = (contentWidth - space) / 2 - imageSize.width / 2 - textSize.width / 2
|
||||
y = contentHeight / 2 - textSize.height / 2
|
||||
width = textSize.width
|
||||
height = textSize.height
|
||||
|
||||
case .up:
|
||||
x = contentWidth / 2 - textSize.width / 2
|
||||
y = (contentHeight - space) / 2 - textSize.height / 2 - imageSize.height / 2
|
||||
width = textSize.width
|
||||
height = textSize.height
|
||||
|
||||
case .down:
|
||||
x = contentWidth / 2 - textSize.width / 2
|
||||
y = (contentHeight - space) / 2 - textSize.height / 2 + imageSize.height / 2 + space
|
||||
width = textSize.width
|
||||
height = textSize.height
|
||||
|
||||
default:
|
||||
x = (contentWidth - space) / 2 + imageSize.width / 2 - textSize.width / 2 + space
|
||||
y = contentHeight / 2 - textSize.height / 2
|
||||
width = textSize.width
|
||||
height = textSize.height
|
||||
}
|
||||
|
||||
self.titleRect = CGRect(x: x, y: y, width: width, height: height)
|
||||
return self.titleRect
|
||||
}
|
||||
|
||||
|
||||
private var borderColors: [UInt : UIColor] = [:]
|
||||
|
||||
func jx_setBorderColor(_ color: UIColor?, for state: UIControl.State) {
|
||||
var arr = self.borderColors
|
||||
let index = state.rawValue
|
||||
arr[index] = color
|
||||
if state == .selected {
|
||||
let i = index + UIControl.State.highlighted.rawValue
|
||||
arr[i] = color
|
||||
}
|
||||
self.borderColors = arr
|
||||
updateStatus()
|
||||
}
|
||||
|
||||
func updateStatus() {
|
||||
var color = self.borderColors[self.state.rawValue]?.cgColor
|
||||
if (color == nil) {
|
||||
color = self.borderColors[UIControl.State.normal.rawValue]?.cgColor
|
||||
}
|
||||
self.layer.borderColor = color
|
||||
}
|
||||
|
||||
override var isSelected: Bool {
|
||||
didSet {
|
||||
updateStatus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension JXButton {
|
||||
|
||||
private func _textSize() -> CGSize {
|
||||
// let size = CGSize(width: self.bounds.size.width, height: self.bounds.size.height)
|
||||
|
||||
var attr: [NSAttributedString.Key : Any] = [:]
|
||||
attr[NSAttributedString.Key.font] = jx_font
|
||||
|
||||
let paragraphStyle = NSMutableParagraphStyle()
|
||||
paragraphStyle.lineBreakMode = .byTruncatingMiddle
|
||||
attr[NSAttributedString.Key.paragraphStyle] = paragraphStyle
|
||||
|
||||
// var rect = self.currentTitle?.ocString.boundingRect(with: size, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: attr, context: nil) ?? .zero
|
||||
|
||||
if let font = jx_font {
|
||||
var size = self.currentTitle?.size(font: font) ?? .zero
|
||||
|
||||
if maxTitleWidth != 0 && maxTitleWidth < size.width {
|
||||
size = CGSize(width: maxTitleWidth, height: size.height)
|
||||
}
|
||||
// if maxTitleWidth != 0 && maxTitleWidth < rect.size.width {
|
||||
// rect.size = CGSize(width: maxTitleWidth, height: rect.size.height)
|
||||
// }
|
||||
return size
|
||||
} else {
|
||||
return .zero
|
||||
}
|
||||
}
|
||||
|
||||
}
|