首页功能开发

This commit is contained in:
曾觉新 2025-04-14 16:32:48 +08:00
parent 0f79f340d9
commit a318806a2d
85 changed files with 1981 additions and 59 deletions

View File

@ -24,6 +24,7 @@ target 'ShortPlay' do
pod 'Toast' #吐司提示
pod 'ZFPlayer/AVPlayer' #播放器
pod 'KTVHTTPCache' #视频缓存
pod 'HWPanModal' #底部弹出控制器
target 'ShortPlayTests' do

View File

@ -1,6 +1,7 @@
PODS:
- Alamofire (5.10.2)
- CocoaAsyncSocket (7.6.5)
- HWPanModal (0.9.9)
- KTVHTTPCache (3.0.2):
- CocoaAsyncSocket
- MJRefresh (3.7.9)
@ -19,6 +20,7 @@ PODS:
- ZFPlayer/Core (4.1.4)
DEPENDENCIES:
- HWPanModal
- KTVHTTPCache
- MJRefresh
- Moya
@ -32,6 +34,7 @@ SPEC REPOS:
trunk:
- Alamofire
- CocoaAsyncSocket
- HWPanModal
- KTVHTTPCache
- MJRefresh
- Moya
@ -44,6 +47,7 @@ SPEC REPOS:
SPEC CHECKSUMS:
Alamofire: 7193b3b92c74a07f85569e1a6c4f4237291e7496
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
HWPanModal: b57a6717d3cdcd666bff44f9dd2a5be9f4d6f5d2
KTVHTTPCache: 5711692cdf9a5ecfe829b1e16577deb3ffe3dc86
MJRefresh: ff9e531227924c84ce459338414550a05d2aea78
Moya: 138f0573e53411fb3dc17016add0b748dfbd78ee
@ -53,6 +57,6 @@ SPEC CHECKSUMS:
YYKit: 7cda43304a8dc3696c449041e2cb3107b4e236e7
ZFPlayer: 5cf39e8d9f0c2394a014b0db4767b5b5a6bffe13
PODFILE CHECKSUM: 422706ab4a12e286d2106acff478496d948912e8
PODFILE CHECKSUM: 6b8807090aa7efc034949fa4dae871806b3d46fd
COCOAPODS: 1.16.2

View File

@ -444,6 +444,7 @@
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ShortPlay/Source/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = ZyreoTV;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UIMainStoryboardFile = "";
@ -478,6 +479,7 @@
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ShortPlay/Source/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = ZyreoTV;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UIMainStoryboardFile = "";

View File

@ -10,7 +10,7 @@ import UIKit
extension AppDelegate {
func appConfig() {
// UIView.et_Awake()
UIView.sp_Awake()
tabBarConfig()
// keyBoardStyle()

View File

@ -23,11 +23,18 @@ class SPViewController: UIViewController, JYPageChildContollerProtocol {
private(set) var isViewDidLoad = false
private(set) var isDidAppear = false
private(set) lazy var _bgImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "main_bg_image_01"))
imageView.isUserInteractionEnabled = true
return imageView;
}()
override func viewDidLoad() {
super.viewDidLoad()
self.isViewDidLoad = true
self.edgesForExtendedLayout = []
self.view.backgroundColor = .black
setBgImageView()
if let navi = navigationController {
if navi.visibleViewController == self {
@ -38,6 +45,11 @@ class SPViewController: UIViewController, JYPageChildContollerProtocol {
}
}
func setBgImageView() {
self.view = self._bgImageView
self.view.backgroundColor = .black
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
isDidAppear = true

View File

@ -56,3 +56,21 @@ public func spLog(message:Any? , file: String = #file, function: String = #funct
print("\n\(Date(timeIntervalSinceNow: 8 * 60 * 60)) \(file.components(separatedBy: "/").last ?? "") \(function) \(line): \(message ?? "")")
#endif
}
public func sp_swizzled_instanceMethod(_ prefix: String, oldClass: Swift.AnyClass!, oldSelector: String, newClass: Swift.AnyClass) {
let newSelector = prefix + "_" + oldSelector;
let originalSelector = NSSelectorFromString(oldSelector)
let swizzledSelector = NSSelectorFromString(newSelector)
let originalMethod = class_getInstanceMethod(oldClass, originalSelector)
let swizzledMethod = class_getInstanceMethod(newClass, swizzledSelector)
let isAdd = class_addMethod(oldClass, originalSelector, method_getImplementation(swizzledMethod!), method_getTypeEncoding(swizzledMethod!))
if isAdd {
class_replaceMethod(newClass, swizzledSelector, method_getImplementation(originalMethod!), method_getTypeEncoding(originalMethod!))
}else {
method_exchangeImplementations(originalMethod!, swizzledMethod!)
}
}

View File

@ -0,0 +1,66 @@
//
// CGMutablePath+SPAdd.swift
// ShortPlay
//
// Created by on 2025/4/14.
//
import UIKit
/** */
struct SPCirculars {
var topLeft:CGFloat = 0
var topRight:CGFloat = 0
var bottomLeft:CGFloat = 0
var bottomRight:CGFloat = 0
public static let zero = SPCirculars(topLeft: 0, topRight: 0, bottomLeft: 0, bottomRight: 0)
public init(topLeft: CGFloat, topRight:CGFloat, bottomLeft:CGFloat, bottomRight:CGFloat) {
self.topLeft = topLeft
self.topRight = topRight
self.bottomLeft = bottomLeft
self.bottomRight = bottomRight
}
static func ==(v1:SPCirculars, v2:SPCirculars) -> Bool {
return v1.bottomLeft == v2.bottomLeft
&& v1.bottomRight == v2.bottomRight
&& v1.topLeft == v2.topLeft
&& v1.topRight == v2.topRight
}
static func !=(v1:SPCirculars, v2:SPCirculars) -> Bool {
return !(v1 == v2)
}
}
extension CGMutablePath {
func addRadiusRectangle(_ circulars: SPCirculars, rect: CGRect) {
let minX = rect.minX
let minY = rect.minY
let maxX = rect.maxX
let maxY = rect.maxY
//
let topLeftCenterX = minX + circulars.topLeft
let topLeftCenterY = minY + circulars.topLeft
let topRightCenterX = maxX - circulars.topRight
let topRightCenterY = minY + circulars.topRight
let bottomLeftCenterX = minX + circulars.bottomLeft
let bottomLeftCenterY = maxY - circulars.bottomLeft
let bottomRightCenterX = maxX - circulars.bottomRight
let bottomRightCenterY = maxY - circulars.bottomRight
//
addArc(center: CGPoint(x: topLeftCenterX, y: topLeftCenterY), radius: circulars.topLeft, startAngle: CGFloat.pi, endAngle: CGFloat.pi * 3 / 2, clockwise: false)
//
addArc(center: CGPoint(x: topRightCenterX, y: topRightCenterY), radius: circulars.topRight, startAngle: CGFloat.pi * 3 / 2, endAngle: 0, clockwise: false)
//
addArc(center: CGPoint(x: bottomRightCenterX, y: bottomRightCenterY), radius: circulars.bottomRight, startAngle: 0, endAngle: CGFloat.pi / 2, clockwise: false)
//
addArc(center: CGPoint(x: bottomLeftCenterX, y: bottomLeftCenterY), radius: circulars.bottomLeft, startAngle: CGFloat.pi / 2, endAngle: CGFloat.pi, clockwise: false)
closeSubpath();
}
}

View File

@ -43,5 +43,25 @@ extension UIColor {
static func color7F7F80(alpha: CGFloat = 1) -> UIColor {
return color(hex: 0x7F7F80, alpha: alpha)
}
static func colorD2D2D2(alpha: CGFloat = 1) -> UIColor {
return color(hex: 0xD2D2D2, alpha: alpha)
}
static func colorBF6BFF(alpha: CGFloat = 1) -> UIColor {
return color(hex: 0xBF6BFF, alpha: alpha)
}
static func color121418(alpha: CGFloat = 1) -> UIColor {
return color(hex: 0x121418, alpha: alpha)
}
static func colorF564B6(alpha: CGFloat = 1) -> UIColor {
return color(hex: 0xF564B6, alpha: alpha)
}
static func colorF56490(alpha: CGFloat = 1) -> UIColor {
return color(hex: 0xF56490, alpha: alpha)
}
}

View File

@ -20,4 +20,8 @@ extension UIFont {
static func fontBold(ofSize: CGFloat) -> UIFont {
return .systemFont(ofSize: ofSize, weight: .bold)
}
static func fontLight(ofSize: CGFloat) -> UIFont {
return .systemFont(ofSize: ofSize, weight: .light)
}
}

View File

@ -0,0 +1,19 @@
//
// UIStackView+SPAdd.swift
// ShortPlay
//
// Created by on 2025/4/14.
//
import UIKit
extension UIStackView {
func removeAllArrangedSubview() {
let arrangedSubviews = self.arrangedSubviews
arrangedSubviews.forEach {
self.removeArrangedSubview($0)
$0.removeFromSuperview()
}
}
}

View File

@ -9,7 +9,103 @@ import UIKit
import SnapKit
extension UIView {
fileprivate struct AssociatedKeys {
static var sp_tapGesture: Int?
static var sp_effect: Int?
static var sp_circulars: Int?
}
@objc public static func sp_Awake() {
sp_swizzled_instanceMethod("sp", oldClass: self, oldSelector: "layoutSubviews", newClass: self)
}
@objc func sp_layoutSubviews() {
sp_layoutSubviews()
_updateRadius()
if let effectView = effectView, effectView.frame != self.bounds {
effectView.frame = self.bounds
}
}
///
func getFirstResponderView() -> UIView? {
var resultView: UIView? = nil
for view in self.subviews {
if view.isFirstResponder {
resultView = view
break
} else {
resultView = view.getFirstResponderView()
if resultView != nil {
break
}
}
}
return resultView
}
}
//MARK: -------------- --------------
extension UIView {
private var effectView: UIVisualEffectView? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.sp_effect) as? UIVisualEffectView
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.sp_effect, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
///
func addEffectView(style: UIBlurEffect.Style = .dark) {
if self.effectView == nil {
let blur = UIBlurEffect(style: style)
let effectView = UIVisualEffectView(effect: blur)
self.addSubview(effectView)
self.sendSubviewToBack(effectView)
self.effectView = effectView
}
}
///
func removeEffectView() {
self.effectView?.removeFromSuperview()
self.effectView = nil
}
}
//MARK: -------------- --------------
extension UIView {
private var circulars: SPCirculars? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.sp_circulars) as? SPCirculars
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.sp_circulars, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
func addRadius(topLeft: CGFloat, topRight: CGFloat, bottomLeft: CGFloat, bottomRight: CGFloat) {
//
self.circulars = SPCirculars(topLeft: topLeft, topRight: topRight, bottomLeft: bottomLeft, bottomRight: bottomRight)
_updateRadius()
}
private func _updateRadius() {
guard let circulars = self.circulars else { return }
let rect = self.bounds
let path = CGMutablePath()
path.addRadiusRectangle(circulars, rect: rect)
let maskLayer = CAShapeLayer()
maskLayer.frame = self.bounds
maskLayer.path = path
self.layer.mask = maskLayer
}
}

View File

@ -25,4 +25,26 @@ class SPHomeAPI: NSObject {
}
///
static func requestHomeTopData(completer: ((_ model: SPHomeTopModel?) -> Void)?) {
let param = SPNetworkParameters(path: "/homeTop")
// param.method = .get
SPNetwork.request(parameters: param) { (response: SPNetworkResponse<SPHomeTopModel>) in
completer?(response.data)
}
}
///
static func requestHomeModuleData(completer: ((_ model: SPHomeModuleModel?) -> Void)?) {
var param = SPNetworkParameters(path: "/homeModuleData")
param.method = .get
SPNetwork.request(parameters: param) { (response: SPNetworkResponse<SPHomeModuleModel>) in
completer?(response.data)
}
}
}

View File

@ -10,15 +10,18 @@ import UIKit
class SPVideoAPI: NSObject {
///
static func requestVideoDetail(videoId: String, shortPlayId: String, completer: ((_ model: SPVideoDetailModel?) -> Void)?) {
var param = SPNetworkParameters(path: "/getVideoDetails")
param.method = .get
param.parameters = [
"video_id" : videoId,
static func requestVideoDetail(videoId: String?, shortPlayId: String, completer: ((_ model: SPVideoDetailModel?) -> Void)?) {
var parameters: [String : Any] = [
"short_play_id" : shortPlayId
]
if let videoId = videoId {
parameters["video_id"] = videoId
}
var param = SPNetworkParameters(path: "/getVideoDetails")
param.method = .get
param.parameters = parameters
SPNetwork.request(parameters: param) { (response: SPNetworkResponse<SPVideoDetailModel>) in
completer?(response.data)
}

View File

@ -9,10 +9,26 @@ import UIKit
#if DEBUG
let SPBaseURL = "https://test1-api.guyantv.com"
let SPWebBaseURL = "https://test1-api.guyantv.com"
let SPWebBaseURL = "https://www.guyantv.com"
#else
let SPBaseURL = "https://test1-api.guyantv.com"
let SPWebBaseURL = "https://test1-api.guyantv.com"
let SPWebBaseURL = "https://www.guyantv.com"
#endif
///
let SPUserAgreementWebUrl = SPWebBaseURL + "/user_policy.htm"
///
let SPPrivacyPolicyWebUrl = SPWebBaseURL + "/private.htm"
///
let SPInformationProtectionWebUrl = SPWebBaseURL + "/information_protection.html"
///
let SPInformationSharingWebUrl = SPWebBaseURL + "/information_sharing.html"
///
let SPPersoInforDisclosureWebUrl = SPWebBaseURL + "/persoInfor_disclosure.html"
///
let SPCivizatioConventionWebUrl = SPWebBaseURL + "/civizatio_convention.html"
///
let SPMemberShipAgreementWebUrl = SPWebBaseURL + "/member_ship_agreement.html"

View File

@ -0,0 +1,44 @@
//
// SPGradientView.swift
// ShortPlay
//
// Created by on 2025/4/14.
//
import UIKit
class SPGradientView: UIView {
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
var gradientLayer: CAGradientLayer {
return self.layer as! CAGradientLayer
}
var locations: [NSNumber]? {
didSet {
self.gradientLayer.locations = locations
}
}
var colors: [CGColor]? {
didSet {
self.gradientLayer.colors = colors
}
}
var startPoint: CGPoint = .zero {
didSet {
self.gradientLayer.startPoint = startPoint
}
}
var endPoint: CGPoint = .zero {
didSet {
self.gradientLayer.endPoint = endPoint
}
}
}

View File

@ -0,0 +1,51 @@
//
// SPTableView.swift
// ShortPlay
//
// Created by on 2025/4/14.
//
import UIKit
class SPTableView: UITableView {
var insetGroupedMargins: CGFloat = 12
override init(frame: CGRect, style: UITableView.Style) {
super.init(frame: frame, style: style)
// separatorColor = .lineColor()
self.backgroundColor = .clear
self.contentInsetAdjustmentBehavior = .never
if style == .insetGrouped {
sectionFooterHeight = 12
sectionHeaderHeight = 0.1
} else if style == .plain {
if #available(iOS 15.0, *) {
sectionHeaderTopPadding = 0
}
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
/// insetGrouped https://github.com/QMUI/QMUIDemo_iOS/blob/master/QMUI/QMUIKit/UIKitExtensions/UITableView%2BQMUI.m
override var layoutMargins: UIEdgeInsets {
set {
super.layoutMargins = newValue
}
get {
var margins = super.layoutMargins
if #available(iOS 13.0, *) {
if self.style == .insetGrouped {
margins.left = self.safeAreaInsets.left + insetGroupedMargins
margins.right = self.safeAreaInsets.right + insetGroupedMargins
}
}
return margins
}
}
}

View File

@ -0,0 +1,83 @@
//
// SPTableViewCell.swift
// ShortPlay
//
// Created by on 2025/4/14.
//
import UIKit
class SPTableViewCell: UITableViewCell {
var indicatorMargin: CGFloat = 7.5 {
didSet {
indicatorImageView.snp.updateConstraints { make in
make.right.equalToSuperview().offset(-indicatorMargin)
}
}
}
var showIndicator = false {
didSet {
indicatorImageView.isHidden = !showIndicator
}
}
private(set) lazy var indicatorImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "right_arrow_icon_01"))
imageView.isHidden = !showIndicator
return imageView
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.layer.rasterizationScale = UIScreen.main.scale
self.layer.shouldRasterize = true
self.selectionStyle = .none
self.backgroundColor = .clear
contentView.addSubview(indicatorImageView)
indicatorImageView.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.right.equalToSuperview().offset(-indicatorMargin)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
extension UITableViewCell {
var sp_tableView: UITableView? {
return self.value(forKey: "_tableView") as? UITableView
}
// MARK: -
public static func registerCell(tableView: UITableView, _ reuseIdentifier: String? = nil) {
let reuseIdentifier = reuseIdentifier == nil ? NSStringFromClass(self) : reuseIdentifier
tableView.register(self, forCellReuseIdentifier: reuseIdentifier!)
}
// MARK: -
public static func dequeueReusableCell(tableView: UITableView, indexPath: IndexPath, _ reuseIdentifier: String? = nil) -> Self {
let reuseIdentifier = reuseIdentifier == nil ? NSStringFromClass(self) : reuseIdentifier
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier!, for: indexPath)
return cell as! Self
}
}

View File

@ -0,0 +1,201 @@
//
// SPWebView.swift
// ShortPlay
//
// Created by on 2025/4/14.
//
import UIKit
@preconcurrency import WebKit
class SPWebView: WKWebView {
weak var delegate: SPWebViewDelegate?
deinit {
self.removeObserver(self, forKeyPath: "estimatedProgress")
self.removeObserver(self, forKeyPath: "title")
}
override init(frame: CGRect, configuration: WKWebViewConfiguration) {
super.init(frame: frame, configuration: configuration)
// addScriptMessageHandler()
_setupInit()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func _setupInit() {
self.isOpaque = false
self.uiDelegate = self
self.navigationDelegate = self
self.addObserver(self, forKeyPath: "estimatedProgress", options: .new, context: nil)
self.addObserver(self, forKeyPath: "title", options: .new, context: nil)
// var userAgent = (self.value(forKey: "userAgent") as? String) ?? ""
// if !userAgent.contains(";jxdbBrowser") {
// userAgent = userAgent + ";jxdbBrowser|V\(kYDAPPVersion)"
// self.customUserAgent = userAgent
// }
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if object as? SPWebView == self {
if keyPath == "estimatedProgress", let progress = change?[NSKeyValueChangeKey.newKey] as? CGFloat {
self.delegate?.webView?(webView: self, didChangeProgress: progress)
} else if keyPath == "title", let title = change?[NSKeyValueChangeKey.newKey] as? String {
self.delegate?.webView?(webView: self, didChangeTitle: title)
}
}
}
func load(urlStr: String) {
guard let url = URL(string: urlStr) else { return }
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 30)
self.load(request)
}
}
//MARK:-------------- WKUIDelegate --------------
extension SPWebView: WKUIDelegate {
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
let alertController = UIAlertController(title: "提示", message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "确认", style: .default, handler: { (action) in
completionHandler()
}))
self.viewController?.present(alertController, animated: true, completion: nil)
}
func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
let alertController = UIAlertController(title: "提示", message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "取消", style: .cancel, handler: { (action) in
completionHandler(false)
}))
alertController.addAction(UIAlertAction(title: "确认", style: .default, handler: { (action) in
completionHandler(true)
}))
self.viewController?.present(alertController, animated: true, completion: nil)
}
func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
let alertController = UIAlertController(title: prompt, message: "", preferredStyle: .alert)
alertController.addTextField { (textField) in
textField.text = defaultText
}
alertController.addAction(UIAlertAction(title: "完成", style: .default, handler: { (action) in
completionHandler(alertController.textFields?.first?.text)
}))
self.viewController?.present(alertController, animated: true, completion: nil)
}
}
//MARK:-------------- WKNavigationDelegate --------------
extension SPWebView: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
/*cookie*/
// let cookies = JXCookiesManager.getAllCookies()
// for cookie in cookies {
// setCookie(cookie: cookie)
// }
decisionHandler(.allow);
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
spLog(message: navigationAction.request.url)
// if navigationAction.request.url?.scheme == "tel" {
// UIApplication.shared.openURL(navigationAction.request.url!)
// decisionHandler(.cancel)
// return
// }
if let url = navigationAction.request.url,
url.scheme != "http",
url.scheme != "https"
{
UIApplication.shared.open(url)
decisionHandler(.cancel)
return
}
//
// if JXRequestHttpProxy.isIntercept() {
// decisionHandler(.cancel)
// return
// }
if let result = self.delegate?.webView?(self, shouldStartLoadWith: navigationAction) {
if result {
decisionHandler(.allow)
} else {
decisionHandler(.cancel)
}
} else {
decisionHandler(.allow)
}
}
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
self.delegate?.webViewDidStartLoad?(self)
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// ///
// webView.evaluateJavaScript("document.documentElement.style.webkitTouchCallout='none';", completionHandler: nil)
// ///
// webView.evaluateJavaScript("document.documentElement.style.webkitUserSelect='none';", completionHandler: nil)
self.delegate?.webViewDidFinishLoad?(self)
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
self.delegate?.webView?(self, didFailLoadWithError: error)
}
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
self.delegate?.webView?(self, didFailLoadWithError: error)
}
}
//MARK:-------------- WKScriptMessageHandler --------------
extension SPWebView: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
self.delegate?.userContentController?(userContentController, didReceive: message)
}
}
//MARK:-------------- YDWebViewDelegate --------------
@objc protocol SPWebViewDelegate: NSObjectProtocol {
@objc optional func webView(_ webView: SPWebView, shouldStartLoadWith navigationAction: WKNavigationAction) -> Bool
@objc optional func webViewDidStartLoad(_ webView: SPWebView)
@objc optional func webViewDidFinishLoad(_ webView: SPWebView)
@objc optional func webView(_ webView: SPWebView, didFailLoadWithError error: Error)
///
@objc optional func webView(webView: SPWebView, didChangeProgress progress: CGFloat)
///
@objc optional func webView(webView: SPWebView, didChangeTitle title: String)
///web
@objc optional func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage)
}

View File

@ -0,0 +1,89 @@
//
// SPWebViewController.swift
// ShortPlay
//
// Created by on 2025/4/14.
//
import UIKit
import WebKit
class SPWebViewController: SPViewController {
var urlStr: String?
private lazy var webView: SPWebView = {
let controller = WKUserContentController()
let config = WKWebViewConfiguration()
config.userContentController = controller
config.preferences.javaScriptEnabled = true
/** JS */
config.preferences.javaScriptCanOpenWindowsAutomatically = true
let webView = SPWebView(frame: self.view.bounds, configuration: config)
webView.delegate = self
return webView
}()
override func viewDidLoad() {
super.viewDidLoad()
configNavigationBack()
_setupUI()
if let url = urlStr {
self.load(urlString: url)
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(false, animated: true)
self.setNavigationNormalStyle()
}
func load(urlString: String) {
// guard let url = URL(string: "https://www.baidu.com") else { return }
var str: String = urlString
// if let userToken = ETLoginManager.manager.userInfo?.userToken {
// if urlString.contains("?") {
// str = urlString + "&userToken=\(userToken)"
// } else {
// str = urlString + "?userToken=\(userToken)"
// }
// }
guard let url = URL(string: str) else { return }
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 30)
// if ETLoginManager.manager.isLogin, let userToken = ETLoginManager.manager.userInfo?.userToken {
// request.setValue(userToken, forHTTPHeaderField: "userToken")
// }
self.webView.load(request)
}
}
extension SPWebViewController {
private func _setupUI() {
self.view.addSubview(webView)
self.webView.frame = self.view.bounds
self.webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
}
//MARK: -------------- ETWebViewDelegate --------------
extension SPWebViewController: SPWebViewDelegate {
func webView(webView: SPWebView, didChangeTitle title: String) {
self.title = title
}
func webView(_ webView: SPWebView, didFailLoadWithError error: any Error) {
spLog(message: error)
}
}

View File

@ -0,0 +1,24 @@
//
// SPHomeChildController.swift
// ShortPlay
//
// Created by on 2025/4/14.
//
import UIKit
class SPHomeChildController: SPViewController {
var topMargins: CGFloat = 10
override func viewDidLoad() {
super.viewDidLoad()
}
override func setBgImageView() { }
}

View File

@ -9,23 +9,56 @@ import UIKit
class SPHomePageController: SPViewController {
private var topModel: SPHomeTopModel?
private lazy var categoryArr: [SPHomeCategoryModel] = {
let arr = [
SPHomeCategoryModel(category_name: "Hot Picks".localized, category_id: nil, viewController: SPHomeViewController()),
SPHomeCategoryModel(category_name: "Top 10".localized, category_id: nil, viewController: nil),
SPHomeCategoryModel(category_name: "Fresh Drops".localized, category_id: nil, viewController: nil),
SPHomeCategoryModel(category_name: "Free".localized, category_id: nil, viewController: nil),
]
return arr
}()
private lazy var pageView: JYPageController = {
let customIndicatorImage = UIImage(named: "page_indicator_icon_01")
let customIndicator = UIImageView(image: customIndicatorImage)
let pageView = JYPageController()
pageView.delegate = self
pageView.dataSource = self
pageView.config.normalTitleColor = .colorD2D2D2()
pageView.config.selectedTitleColor = .colorBF6BFF()
pageView.config.normalTitleFont = 14
pageView.config.selectedTitleFont = 16
pageView.config.normalTitleFontWeight = .regular
pageView.config.selectedTitleFontWeight = .medium
pageView.config.alignment = .scatter
pageView.config.customIndicator = customIndicator
pageView.config.indicatorStyle = .customView
pageView.config.indicatorWidth = customIndicatorImage?.size.width ?? 0
pageView.config.indicatorHeight = customIndicatorImage?.size.height ?? 0
pageView.config.leftPadding = 15
pageView.config.rightPadding = 15
pageView.config.itemsMargin = 24
return pageView
}()
override func viewDidLoad() {
super.viewDidLoad()
sp_setupUI()
requestData()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(false, animated: true)
self.navigationController?.setNavigationBarHidden(true, animated: true)
}
}
@ -37,7 +70,9 @@ extension SPHomePageController {
view.addSubview(pageView.view)
pageView.view.snp.makeConstraints { make in
make.edges.equalToSuperview()
// make.edges.equalToSuperview()
make.top.equalToSuperview().offset(kSPStatusbarHeight + 66)
make.left.right.bottom.equalToSuperview()
}
}
}
@ -45,26 +80,47 @@ extension SPHomePageController {
//MARK: -------------- JYPageControllerDelegate & JYPageControllerDataSource --------------
extension SPHomePageController: JYPageControllerDelegate, JYPageControllerDataSource {
func pageController(_ pageController: JYPageController, frameForSegmentedView segmentedView: JYSegmentedView) -> CGRect {
return .init(x: 0, y: kSPStatusbarHeight + 10, width: kSPScreenWidth, height: 40)
return .init(x: 0, y: 0, width: kSPScreenWidth, height: 40)
}
func pageController(_ pageController: JYPageController, frameForContainerView container: UIScrollView) -> CGRect {
return .init(x: 0, y: 0, width: kSPScreenWidth, height: kSPScreenHeight - kSPTabBarHeight)
return .init(x: 0, y: 40, width: kSPScreenWidth, height: kSPScreenHeight - kSPTabBarHeight - kSPStatusbarHeight - 66 - 40)
}
func pageController(_ pageController: JYPageController, titleAt index: Int) -> String {
return "123"
return self.categoryArr[index].category_name ?? ""
}
func childController(atIndex index: Int) -> any JYPageChildContollerProtocol {
return SPViewController()
let vc = SPHomeViewController()
vc.topMargins = 15
return vc
}
func numberOfChildControllers() -> Int {
return 0
return self.categoryArr.count
}
}
extension SPHomePageController {
private func requestData() {
if self.topModel != nil { return }
SPHomeAPI.requestHomeTopData { [weak self] model in
guard let self = self else { return }
if let model = model {
self.topModel = model
if let category = self.topModel?.category {
self.categoryArr += category
}
self.pageView.reload()
}
}
}
}

View File

@ -7,15 +7,88 @@
import UIKit
class SPHomeViewController: SPViewController {
class SPHomeViewController: SPHomeChildController {
///
private var moduleModel: SPHomeModuleModel?
private lazy var layout: UICollectionViewFlowLayout = {
let layout = UICollectionViewFlowLayout()
return layout
}()
private lazy var collectionView: SPCollectionView = {
let collectionView = SPCollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(SPHomeHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "headerView")
SPCollectionViewCell.registerCell(collectionView: collectionView)
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
// view.backgroundColor = .clear
requestModuleData()
_setupUI()
}
override func fetchChildControllerScrollView() -> UIScrollView? {
return self.collectionView
}
}
extension SPHomeViewController {
private func _setupUI() {
view.addSubview(self.collectionView);
self.collectionView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.top.equalToSuperview().offset(topMargins)
}
}
}
//MARK: -------------- UICollectionViewDelegate & UICollectionViewDataSource --------------
extension SPHomeViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = SPCollectionViewCell.dequeueReusableCell(collectionView: collectionView, indexPath: indexPath)
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 0
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "headerView", for: indexPath) as? SPHomeHeaderView {
headerView.moduleModel = self.moduleModel
return headerView
}
return UICollectionReusableView()
}
}
extension SPHomeViewController {
private func requestModuleData() {
SPHomeAPI.requestHomeModuleData { [weak self] model in
guard let self = self else { return }
if let model = model {
self.moduleModel = model
self.layout.headerReferenceSize = CGSize(width: kSPScreenWidth, height: SPHomeHeaderView.contentHeight(model: model))
self.collectionView.reloadData()
}
}
}
}

View File

@ -6,11 +6,33 @@
//
import UIKit
import SmartCodable
class SPHomeCategoryModel: SPModel {
//class SPHomeCategoryModel: SPModel, SmartCodable {
//
// var category_name: String?
// var category_id: String?
//
// @IgnoredKey
// var viewController: SPViewController?
//
//
// init(category_name: String? = nil, category_id: String? = nil, viewController: SPViewController? = nil) {
// self.category_name = category_name
// self.category_id = category_id
// self.viewController = viewController
// }
//
// required init() {
// fatalError("init() has not been implemented")
// }
//}
struct SPHomeCategoryModel: SmartCodable {
var category_name: String?
var category_id: String?
@IgnoredKey
var viewController: SPViewController?
}

View File

@ -0,0 +1,23 @@
//
// SPHomeModuleModel.swift
// ShortPlay
//
// Created by on 2025/4/14.
//
import UIKit
import SmartCodable
class SPHomeModuleModel: SPModel, SmartCodable {
var bannerData: [SPShortModel]?
///
var recommandData: [SPShortModel]?
///
var manualNewestRecommand: [SPShortModel]?
///
var hotData: [SPShortModel]?
}

View File

@ -0,0 +1,15 @@
//
// SPHomeTopModel.swift
// ShortPlay
//
// Created by on 2025/4/14.
//
import UIKit
import SmartCodable
class SPHomeTopModel: SPModel, SmartCodable {
var category: [SPHomeCategoryModel]?
}

View File

@ -0,0 +1,78 @@
//
// SPHomeBannerCell.swift
// ShortPlay
//
// Created by on 2025/4/14.
//
import UIKit
class SPHomeBannerCell: ZKCycleScrollViewCell {
var model: SPShortModel? {
didSet {
coverImageView.sp_setImage(url: model?.horizontally_img)
titleLabel.text = model?.name
}
}
private lazy var coverImageView: SPImageView = {
let imageView = SPImageView()
return imageView
}()
private lazy var bottomView: SPGradientView = {
let view = SPGradientView()
view.colors = [UIColor.color121418(alpha: 0).cgColor, UIColor.color121418(alpha: 0.8).cgColor]
view.startPoint = CGPoint(x: 0.5, y: 0)
view.endPoint = CGPoint(x: 0.5, y: 1)
view.locations = [0, 1]
return view
}()
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .fontLight(ofSize: 14)
label.textColor = .colorFFFFFF(alpha: 0.9)
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.layer.cornerRadius = 12
contentView.layer.masksToBounds = true
_setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension SPHomeBannerCell {
private func _setupUI() {
contentView.addSubview(coverImageView)
contentView.addSubview(bottomView)
contentView.addSubview(titleLabel)
coverImageView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
bottomView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.height.equalTo(65)
}
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(7)
make.bottom.equalToSuperview().offset(-12)
make.right.lessThanOrEqualToSuperview().offset(-7)
}
}
}

View File

@ -0,0 +1,87 @@
//
// SPHomeDataItemView.swift
// ShortPlay
//
// Created by on 2025/4/14.
//
import UIKit
class SPHomeDataItemView: UIView {
///
static let contentToTop: CGFloat = 36
class func contentHeight(dataArr: [SPShortModel]) -> CGFloat {
return contentToTop
}
override var intrinsicContentSize: CGSize {
let height = Self.contentHeight(dataArr: dataArr ?? [])
return CGSize(width: kSPScreenWidth, height: height)
}
var dataArr: [SPShortModel]? {
didSet {
self.invalidateIntrinsicContentSize()
}
}
private(set) lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .fontMedium(ofSize: 15)
label.textColor = .colorFFFFFF()
return label
}()
private lazy var moreButton: UIButton = {
let button = UIButton(type: .custom)
button.setTitle("More", for: .normal)
button.setTitleColor(.colorF564B6(), for: .normal)
button.titleLabel?.font = .fontLight(ofSize: 12)
return button
}()
private(set) lazy var contentView: UIView = {
let view = UIView()
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
_setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension SPHomeDataItemView {
private func _setupUI() {
addSubview(titleLabel)
addSubview(moreButton)
addSubview(contentView)
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
make.top.equalToSuperview()
make.height.equalTo(21)
}
moreButton.snp.makeConstraints { make in
make.centerY.equalTo(moreButton)
make.right.equalToSuperview().offset(-15)
}
contentView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.top.equalTo(Self.contentToTop)
}
}
}

View File

@ -0,0 +1,141 @@
//
// SPHomeHeaderView.swift
// ShortPlay
//
// Created by on 2025/4/14.
//
import UIKit
class SPHomeHeaderView: UICollectionReusableView {
var moduleModel: SPHomeModuleModel? {
didSet {
stackView.removeAllArrangedSubview()
stackView.addArrangedSubview(bannerView)
bannerView.reloadData()
// if (moduleModel?.recommandData?.count ?? 0) > 0 {
// }
stackView.addArrangedSubview(trendingView)
trendingView.dataArr = moduleModel?.bannerData
stackView.addArrangedSubview(hotView)
hotView.dataArr = moduleModel?.bannerData
stackView.addArrangedSubview(shortsForYouView)
shortsForYouView.dataArr = moduleModel?.bannerData
}
}
private lazy var stackView: UIStackView = {
let view = UIStackView()
view.axis = .vertical
view.spacing = 25
return view
}()
private lazy var bannerView: ZKCycleScrollView = {
let bannerView = ZKCycleScrollView(frame: .zero, shouldInfiniteLoop: true);
bannerView.delegate = self
bannerView.dataSource = self
bannerView.itemSpacing = 10
bannerView.itemSize = CGSize(width: kSPScreenWidth - 30, height: Self.bannerHeight())
bannerView.register(SPHomeBannerCell.self, forCellWithReuseIdentifier: "bannerCell")
bannerView.hidesPageControl = true
bannerView.snp.makeConstraints { make in
make.width.equalTo(kSPScreenWidth)
make.height.equalTo(Self.bannerHeight())
}
return bannerView
}()
private lazy var trendingView: SPHomeTrendingView = {
let view = SPHomeTrendingView()
return view
}()
private lazy var hotView: SPHomeHotView = {
let view = SPHomeHotView()
return view
}()
private lazy var shortsForYouView: SPHomeShortsForYouView = {
let view = SPHomeShortsForYouView()
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
_setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension SPHomeHeaderView {
private func _setupUI() {
addSubview(stackView)
stackView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
}
//MARK: -------------- ZKCycleScrollViewDelegate & ZKCycleScrollViewDataSource --------------
extension SPHomeHeaderView: ZKCycleScrollViewDelegate, ZKCycleScrollViewDataSource {
func numberOfItems(in cycleScrollView: ZKCycleScrollView) -> Int {
return moduleModel?.bannerData?.count ?? 0
}
func cycleScrollView(_ cycleScrollView: ZKCycleScrollView, cellForItemAt index: Int) -> ZKCycleScrollViewCell {
let cell = cycleScrollView.dequeueReusableCell(withReuseIdentifier: "bannerCell", for: index) as! SPHomeBannerCell
cell.model = moduleModel?.bannerData?[index]
return cell
}
func cycleScrollView(_ cycleScrollView: ZKCycleScrollView, didSelectItemAt index: Int) {
let model = moduleModel?.bannerData?[index]
let vc = SPTVPlayerListViewController()
vc.shortPlayId = model?.short_play_id
self.viewController?.navigationController?.pushViewController(vc, animated: true)
}
}
extension SPHomeHeaderView {
static func contentHeight(model: SPHomeModuleModel) -> CGFloat {
var height = bannerHeight()
// if (model.recommandData?.count ?? 0) > 0 {
// }
height = height + SPHomeTrendingView.contentHeight(dataArr: model.bannerData ?? []) + 25
height = height + SPHomeHotView.contentHeight(dataArr: model.bannerData ?? []) + 25
height = height + SPHomeShortsForYouView.contentHeight(dataArr: model.bannerData ?? []) + 25
return height
}
static func bannerHeight() -> CGFloat {
return 183
}
}

View File

@ -0,0 +1,96 @@
//
// SPHomeHotCell.swift
// ShortPlay
//
// Created by on 2025/4/14.
//
import UIKit
class SPHomeHotCell: SPCollectionViewCell {
var model: SPShortModel? {
didSet {
coverImageView.sp_setImage(url: model?.image_url)
titleLabel.text = model?.name
desLabel.text = model?.sp_description
}
}
private lazy var bgView: UIView = {
let view = UIView()
view.backgroundColor = .colorFFFFFF(alpha: 0.1)
view.layer.cornerRadius = 12
view.layer.masksToBounds = true
return view
}()
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 = .fontLight(ofSize: 14)
label.textColor = .colorFFFFFF(alpha: 0.9)
label.numberOfLines = 2
return label
}()
private lazy var desLabel: UILabel = {
let label = UILabel()
label.font = .fontLight(ofSize: 14)
label.textColor = .colorFFFFFF(alpha: 0.28)
label.numberOfLines = 2
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension SPHomeHotCell {
private func _setupUI() {
contentView.addSubview(bgView)
contentView.addSubview(coverImageView)
contentView.addSubview(titleLabel)
contentView.addSubview(desLabel)
bgView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.height.equalTo(116)
}
coverImageView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
make.top.equalToSuperview()
make.width.equalTo(94)
make.height.equalTo(124)
}
titleLabel.snp.makeConstraints { make in
make.left.equalTo(coverImageView.snp.right).offset(13)
make.top.equalTo(bgView).offset(10)
make.right.lessThanOrEqualToSuperview().offset(-10)
}
desLabel.snp.makeConstraints { make in
make.left.equalTo(titleLabel)
make.bottom.equalTo(coverImageView)
make.right.lessThanOrEqualToSuperview().offset(-10)
}
}
}

View File

@ -0,0 +1,91 @@
//
// SPHomeHotView.swift
// ShortPlay
//
// Created by on 2025/4/14.
//
import UIKit
class SPHomeHotView: SPHomeDataItemView {
override class func contentHeight(dataArr: [SPShortModel]) -> CGFloat {
if dataArr.count == 1 {
return 139 + contentToTop
} else {
return 139 * 2 + 15 + contentToTop
}
}
override var dataArr: [SPShortModel]? {
didSet {
self.collectionView.reloadData()
}
}
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.itemSize = CGSize(width: kSPScreenWidth - 15 - 20, height: 139)
layout.sectionInset = UIEdgeInsets(top: 0, left: 15, bottom: 0, right: 15)
layout.minimumLineSpacing = 10
layout.minimumInteritemSpacing = 15
return layout
}()
private lazy var collectionView: SPCollectionView = {
let collectionView = SPCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.showsVerticalScrollIndicator = false
collectionView.showsHorizontalScrollIndicator = false
SPHomeHotCell.registerCell(collectionView: collectionView)
return collectionView
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.titleLabel.text = "Editor's Hotlist".localized
_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension SPHomeHotView {
private func _setupUI() {
contentView.addSubview(collectionView)
collectionView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
}
//MARK: -------------- UICollectionViewDelegate & UICollectionViewDataSource --------------
extension SPHomeHotView: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = SPHomeHotCell.dequeueReusableCell(collectionView: collectionView, indexPath: indexPath)
cell.model = dataArr?[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataArr?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let model = self.dataArr?[indexPath.row]
let vc = SPTVPlayerListViewController()
vc.shortPlayId = model?.short_play_id
self.viewController?.navigationController?.pushViewController(vc, animated: true)
}
}

View File

@ -0,0 +1,80 @@
//
// SPHomeShortsForYouCell.swift
// ShortPlay
//
// Created by on 2025/4/14.
//
import UIKit
class SPHomeShortsForYouCell: SPCollectionViewCell {
var model: SPShortModel? {
didSet {
coverImageView.sp_setImage(url: model?.horizontally_img)
titleLabel.text = model?.name
}
}
private lazy var coverImageView: SPImageView = {
let imageView = SPImageView()
return imageView
}()
private lazy var bottomView: SPGradientView = {
let view = SPGradientView()
view.colors = [UIColor.color121418(alpha: 0).cgColor, UIColor.color121418(alpha: 0.8).cgColor]
view.startPoint = CGPoint(x: 0.5, y: 0)
view.endPoint = CGPoint(x: 0.5, y: 1)
view.locations = [0, 1]
return view
}()
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .fontLight(ofSize: 14)
label.textColor = .colorFFFFFF(alpha: 0.9)
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.layer.cornerRadius = 12
contentView.layer.masksToBounds = true
_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension SPHomeShortsForYouCell {
private func _setupUI() {
contentView.addSubview(coverImageView)
contentView.addSubview(bottomView)
contentView.addSubview(titleLabel)
coverImageView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
bottomView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.height.equalTo(65)
}
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(7)
make.bottom.equalToSuperview().offset(-12)
make.right.lessThanOrEqualToSuperview().offset(-7)
}
}
}

View File

@ -0,0 +1,81 @@
//
// SPHomeShortsForYouView.swift
// ShortPlay
//
// Created by on 2025/4/14.
//
import UIKit
class SPHomeShortsForYouView: SPHomeDataItemView {
override class func contentHeight(dataArr: [SPShortModel]) -> CGFloat {
return 271 + contentToTop
}
override var dataArr: [SPShortModel]? {
didSet {
self.collectionView.reloadData()
}
}
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.itemSize = CGSize(width: 204, height: 271)
layout.sectionInset = UIEdgeInsets(top: 0, left: 15, bottom: 0, right: 15)
layout.minimumLineSpacing = 15
layout.minimumInteritemSpacing = 15
return layout
}()
private lazy var collectionView: SPCollectionView = {
let collectionView = SPCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.showsVerticalScrollIndicator = false
collectionView.showsHorizontalScrollIndicator = false
SPHomeShortsForYouCell.registerCell(collectionView: collectionView)
return collectionView
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.titleLabel.text = "Shorts for You".localized
_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension SPHomeShortsForYouView {
private func _setupUI() {
contentView.addSubview(collectionView)
collectionView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
}
//MARK: -------------- UICollectionViewDelegate & UICollectionViewDataSource --------------
extension SPHomeShortsForYouView: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = SPHomeShortsForYouCell.dequeueReusableCell(collectionView: collectionView, indexPath: indexPath)
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataArr?.count ?? 0
}
}

View File

@ -0,0 +1,91 @@
//
// SPHomeTrendingCell.swift
// ShortPlay
//
// Created by on 2025/4/14.
//
import UIKit
class SPHomeTrendingCell: SPCollectionViewCell {
var model: SPShortModel? {
didSet {
coverImageView.sp_setImage(url: model?.image_url)
titleLabel.text = model?.name
}
}
private lazy var coverImageView: SPImageView = {
let imageView = SPImageView()
imageView.layer.cornerRadius = 10
imageView.layer.masksToBounds = true
return imageView
}()
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .fontLight(ofSize: 13)
label.textColor = .colorFFFFFF(alpha: 0.9)
label.numberOfLines = 2
return label
}()
private lazy var hotBgView: SPGradientView = {
let view = SPGradientView()
view.colors = [UIColor.colorF56490().cgColor, UIColor.colorBF6BFF().cgColor]
view.startPoint = .init(x: 0.5, y: 0)
view.endPoint = .init(x: 0.5, y: 1)
view.locations = [0, 1]
view.addRadius(topLeft: 0, topRight: 0, bottomLeft: 0, bottomRight: 6)
return view
}()
private lazy var hotIconImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "hot_icon_01"))
return imageView
}()
override init(frame: CGRect) {
super.init(frame: frame)
_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension SPHomeTrendingCell {
private func _setupUI() {
contentView.addSubview(coverImageView)
contentView.addSubview(titleLabel)
coverImageView.addSubview(hotBgView)
hotBgView.addSubview(hotIconImageView)
coverImageView.snp.makeConstraints { make in
make.left.right.top.equalToSuperview()
make.bottom.equalToSuperview().offset(-38)
}
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview()
make.right.lessThanOrEqualToSuperview()
make.top.equalTo(coverImageView.snp.bottom).offset(5)
}
hotBgView.snp.makeConstraints { make in
make.left.top.equalToSuperview()
make.width.equalTo(22)
make.height.equalTo(16)
}
hotIconImageView.snp.makeConstraints { make in
make.center.equalToSuperview()
}
}
}

View File

@ -0,0 +1,108 @@
//
// SPHomeTrendingView.swift
// ShortPlay
//
// Created by on 2025/4/14.
//
import UIKit
class SPHomeTrendingView: SPHomeDataItemView {
private static func itemSize() -> CGSize {
let width = floor((kSPScreenWidth - 15 * 2 - 12 * 2) / 3)
let imageScale: CGFloat = 153 / 107
let height = width * imageScale + 38
return CGSize(width: width, height: height)
}
override class func contentHeight(dataArr: [SPShortModel]) -> CGFloat {
var height = self.contentToTop
var lineCount = dataArr.count / 3
if dataArr.count % 3 > 0 {
lineCount += 1
}
let contentHeight = itemSize().height * CGFloat(lineCount) + 12 * CGFloat(lineCount - 1)
if contentHeight > 0 {
height += contentHeight
} else {
height += 1
}
return height
}
override var dataArr: [SPShortModel]? {
didSet {
self.collectionView.reloadData()
}
}
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 12
layout.minimumInteritemSpacing = 12
layout.itemSize = Self.itemSize()
layout.sectionInset = UIEdgeInsets(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
collectionView.isScrollEnabled = false
SPHomeTrendingCell.registerCell(collectionView: collectionView)
return collectionView
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.titleLabel.text = "Trending Now".localized
_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension SPHomeTrendingView {
private func _setupUI() {
contentView.addSubview(collectionView)
collectionView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
}
//MARK: -------------- UICollectionViewDataSource & UICollectionViewDelegate --------------
extension SPHomeTrendingView: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = SPHomeTrendingCell.dequeueReusableCell(collectionView: collectionView, indexPath: indexPath)
cell.model = dataArr?[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataArr?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let model = self.dataArr?[indexPath.row]
let vc = SPTVPlayerListViewController()
vc.shortPlayId = model?.short_play_id
self.viewController?.navigationController?.pushViewController(vc, animated: true)
}
}

View File

@ -9,6 +9,8 @@ import UIKit
class SPMineViewController: SPViewController {
override func viewDidLoad() {
super.viewDidLoad()

View File

@ -160,7 +160,10 @@ extension SPPlayerListViewController {
view.addSubview(collectionView)
collectionView.snp.makeConstraints { make in
make.edges.equalToSuperview()
// make.edges.equalToSuperview()
make.center.equalToSuperview()
make.width.equalTo(self.contentSize.width)
make.height.equalTo(self.contentSize.height)
}
}

View File

@ -69,7 +69,7 @@ extension SPTVPlayerListViewController: SPPlayerListViewControllerDataSource {
extension SPTVPlayerListViewController {
private func requestDetailData() {
guard let videoId = self.videoId, let shortPlayId = self.shortPlayId else { return }
guard let shortPlayId = self.shortPlayId else { return }
SPVideoAPI.requestVideoDetail(videoId: videoId, shortPlayId: shortPlayId) { [weak self] model in
guard let self = self else { return }

View File

@ -10,13 +10,13 @@ import SmartCodable
class SPShortModel: SPModel, SmartCodable {
var id: String?
var all_coins: String?
var buy_type: String?
var collect_total: String?
var sp_description: String?
var episode_total: Int?
var horizontally_img: String?
var id: String?
var image_url: String?
var is_collect: Bool?
var name: String?

View File

@ -123,13 +123,13 @@ extension SPPlayerControlView {
}
@objc private func hadlePlayAndOrPaused() {
// self.viewModel?.handlePauseOrPlay?()
self.viewModel?.handlePauseOrPlay?()
guard let model = model as? SPShortModel else { return }
let vc = SPTVPlayerListViewController()
vc.shortPlayId = model.short_play_id
vc.videoId = model.video_info?.short_play_video_id
SPAPPTool.topViewController()?.navigationController?.pushViewController(vc, animated: true)
// guard let model = model as? SPShortModel else { return }
// let vc = SPTVPlayerListViewController()
// vc.shortPlayId = model.short_play_id
// vc.videoId = model.video_info?.short_play_video_id
// SPAPPTool.topViewController()?.navigationController?.pushViewController(vc, animated: true)
}

View File

@ -243,7 +243,7 @@ extension SPPlayer {
player.playerDidToEnd = { [weak self] (asset) in
guard let self = self else { return }
if isLoop {
self.player.replay()
self.replay()
} else {
self.isPlaying = false
self.delegate?.sp_playCompletion?(self)

View File

@ -5,12 +5,12 @@
"scale" : "1x"
},
{
"filename" : "Frame@2x.png",
"filename" : "home@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Frame@3x.png",
"filename" : "home@3x.png",
"idiom" : "universal",
"scale" : "3x"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 957 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -5,12 +5,12 @@
"scale" : "1x"
},
{
"filename" : "Frame@2x.png",
"filename" : "home@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Frame@3x.png",
"filename" : "home@3x.png",
"idiom" : "universal",
"scale" : "3x"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 636 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -5,12 +5,12 @@
"scale" : "1x"
},
{
"filename" : "Frame@2x.png",
"filename" : "Explore@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Frame@3x.png",
"filename" : "Explore@3x.png",
"idiom" : "universal",
"scale" : "3x"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -5,12 +5,12 @@
"scale" : "1x"
},
{
"filename" : "Frame@2x.png",
"filename" : "Explore@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Frame@3x.png",
"filename" : "Explore@3x.png",
"idiom" : "universal",
"scale" : "3x"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -5,12 +5,12 @@
"scale" : "1x"
},
{
"filename" : "Frame@2x.png",
"filename" : "Me@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Frame@3x.png",
"filename" : "Me@3x.png",
"idiom" : "universal",
"scale" : "3x"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -5,12 +5,12 @@
"scale" : "1x"
},
{
"filename" : "Frame@2x.png",
"filename" : "Me@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Frame@3x.png",
"filename" : "Me@3x.png",
"idiom" : "universal",
"scale" : "3x"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 799 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "hot@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "hot@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 931 B

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "路径 497@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "路径 497@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 843 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "bg@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "bg@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 KiB

View File

@ -10,3 +10,10 @@
"For You" = "For You";
"Error" = "Error";
"Profile" = "Profile";
"Hot Picks" = "Hot Picks";
"Top 10" = "Top 10";
"Fresh Drops" = "Fresh Drops";
"Free" = "Free";
"Trending Now" = "Trending Now";
"Editor's Hotlist" = "Editor's Hotlist";
"Shorts for You" = "Shorts for You";

View File

@ -14,6 +14,8 @@ import UIKit
}
@objc public enum JYSegmentedViewAlignment: Int {
///
case scatter
case left
case right
case center

View File

@ -541,8 +541,8 @@ extension JYPageController: UITableViewDelegate, UITableViewDataSource {
cell.backgroundColor = .clear
cell.selectionStyle = .none
// pageContentScrollView.frame = CGRect(x: 0, y: 0, width: childControllerViewFrame.width, height: childControllerViewFrame.height)
cell.contentView.addSubview(segmentedView)
cell.contentView.addSubview(pageContentScrollView)
cell.contentView.addSubview(segmentedView)
return cell
}
}

View File

@ -523,12 +523,17 @@ public class JYSegmentedView: UIView {
private func updateItemsFrame() {
let width = calculateTotalWidth()
var startX: CGFloat = config.leftPadding
if config.alignment == .center, width < frame.width {
startX = (frame.width - width)/2 + config.leftPadding
}
var itemsMargin = config.itemsMargin
if config.alignment == .right, width < frame.width {
if config.alignment == .center, width < frame.width {
startX = (frame.width - width)/2
} else if config.alignment == .right, width < frame.width {
startX = frame.width - width
} else if config.alignment == .scatter, width < frame.width {
let itemTotalWidth = itemTotalWidth()
itemsMargin = (self.frame.size.width - itemTotalWidth - config.leftPadding - config.rightPadding) / CGFloat(items.count - 1)
}
var totalWidth: CGFloat = 0
@ -538,9 +543,9 @@ public class JYSegmentedView: UIView {
item.badgeView?.frame = CGRect(x: item.frame.width + config.badgeViewOffset.x, y: item.frame.origin.y - item.badgeViewHeight/2 + config.badgeViewOffset.y, width: item.badgeViewWidth, height: item.badgeViewHeight)
totalWidth = startX + item.frame.width
}else{
item.frame = CGRect(x: totalWidth + config.itemsMargin , y: item.frame.origin.y, width: item.frame.width, height: item.frame.height)
item.frame = CGRect(x: totalWidth + itemsMargin , y: item.frame.origin.y, width: item.frame.width, height: item.frame.height)
item.badgeView?.frame = CGRect(x: item.frame.width + item.frame.origin.x + config.badgeViewOffset.x, y: item.frame.origin.y - item.badgeViewHeight/2 + config.badgeViewOffset.y, width: item.badgeViewWidth, height: item.badgeViewHeight)
totalWidth = totalWidth + item.frame.width + config.itemsMargin
totalWidth = totalWidth + item.frame.width + itemsMargin
}
let bgView = self.itemBackgroundViews[index]
@ -556,6 +561,14 @@ public class JYSegmentedView: UIView {
contentView.contentSize = CGSize(width: totalWidth + config.rightPadding, height: frame.size.height)
}
private func itemTotalWidth() -> CGFloat {
var totalWidth: CGFloat = 0
for (index,item) in items.enumerated() {
totalWidth = totalWidth + item.frame.width
}
return totalWidth
}
private func calculateTotalWidth() -> CGFloat {
var totalWidth: CGFloat = 0
for (index,item) in items.enumerated() {