feat: 添加动态发布功能及相关文档
主要变更: 1. 新增 EPImageUploader.swift 和 EPProgressHUD.swift,提供图片批量上传和进度显示功能。 2. 新建 EPMomentAPISwiftHelper.swift,封装动态 API 的 Swift 版本。 3. 更新 EPMomentPublishViewController,集成新上传功能并实现发布成功通知。 4. 创建多个文档,包括实施报告、检查清单和快速使用指南,详细记录功能实现和使用方法。 5. 更新 Bridging Header,确保 Swift 和 Objective-C 代码的互操作性。 此功能旨在提升用户体验,简化动态发布流程,并提供清晰的文档支持。
This commit is contained in:
163
YuMi/E-P/Common/EPImageUploader.swift
Normal file
163
YuMi/E-P/Common/EPImageUploader.swift
Normal file
@@ -0,0 +1,163 @@
|
||||
//
|
||||
// EPImageUploader.swift
|
||||
// YuMi
|
||||
//
|
||||
// Created by AI on 2025-10-11.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Foundation
|
||||
|
||||
/// 图片批量上传工具(纯 Swift 内部类,直接使用 QCloudCOSXML SDK)
|
||||
/// 不对外暴露,由 EPSDKManager 内部调用
|
||||
class EPImageUploader {
|
||||
|
||||
init() {}
|
||||
|
||||
/// 批量上传图片(内部方法)
|
||||
/// - Parameters:
|
||||
/// - images: 要上传的图片数组
|
||||
/// - bucket: QCloud bucket 名称
|
||||
/// - customDomain: 自定义域名
|
||||
/// - progress: 进度回调 (已上传数, 总数)
|
||||
/// - success: 成功回调
|
||||
/// - failure: 失败回调
|
||||
func performBatchUpload(
|
||||
_ images: [UIImage],
|
||||
bucket: String,
|
||||
customDomain: String,
|
||||
progress: @escaping (Int, Int) -> Void,
|
||||
success: @escaping ([[String: Any]]) -> Void,
|
||||
failure: @escaping (String) -> Void
|
||||
) {
|
||||
let total = images.count
|
||||
let queue = DispatchQueue(label: "com.yumi.imageupload", attributes: .concurrent)
|
||||
let semaphore = DispatchSemaphore(value: 3) // 最多同时上传 3 张
|
||||
var uploadedCount = 0
|
||||
var resultList: [[String: Any]] = []
|
||||
var hasError = false
|
||||
let lock = NSLock()
|
||||
|
||||
for (_, image) in images.enumerated() {
|
||||
queue.async {
|
||||
semaphore.wait()
|
||||
|
||||
// 检查是否已经失败
|
||||
lock.lock()
|
||||
if hasError {
|
||||
lock.unlock()
|
||||
semaphore.signal()
|
||||
return
|
||||
}
|
||||
lock.unlock()
|
||||
|
||||
// 压缩图片
|
||||
guard let imageData = image.jpegData(compressionQuality: 0.5) else {
|
||||
lock.lock()
|
||||
hasError = true
|
||||
lock.unlock()
|
||||
semaphore.signal()
|
||||
DispatchQueue.main.async {
|
||||
failure("图片压缩失败")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 获取图片格式
|
||||
let format = UIImage.getImageType(withImageData: imageData) ?? "jpeg"
|
||||
|
||||
// 生成文件名
|
||||
let uuid = NSString.createUUID()
|
||||
let fileName = "image/\(uuid).\(format)"
|
||||
|
||||
// 直接使用 QCloud SDK 上传
|
||||
let request = QCloudCOSXMLUploadObjectRequest<AnyObject>()
|
||||
request.bucket = bucket
|
||||
request.object = fileName
|
||||
request.body = imageData as NSData
|
||||
|
||||
// 监听上传进度(可选)
|
||||
request.sendProcessBlock = { bytesSent, totalBytesSent, totalBytesExpectedToSend in
|
||||
// 单个文件的上传进度(当前不使用)
|
||||
}
|
||||
|
||||
// 监听上传结果
|
||||
request.finishBlock = { [weak self] result, error in
|
||||
guard let self = self else {
|
||||
semaphore.signal()
|
||||
return
|
||||
}
|
||||
|
||||
if let error = error {
|
||||
// 上传失败
|
||||
lock.lock()
|
||||
if !hasError {
|
||||
hasError = true
|
||||
lock.unlock()
|
||||
semaphore.signal()
|
||||
DispatchQueue.main.async {
|
||||
failure(error.localizedDescription)
|
||||
}
|
||||
} else {
|
||||
lock.unlock()
|
||||
semaphore.signal()
|
||||
}
|
||||
} else if let result = result as? QCloudUploadObjectResult {
|
||||
// 上传成功
|
||||
lock.lock()
|
||||
if !hasError {
|
||||
uploadedCount += 1
|
||||
|
||||
// 解析上传 URL(参考 UploadFile.m line 217-223)
|
||||
let uploadedURL = self.parseUploadURL(result.location, customDomain: customDomain)
|
||||
|
||||
let imageInfo: [String: Any] = [
|
||||
"resUrl": uploadedURL,
|
||||
"width": image.size.width,
|
||||
"height": image.size.height,
|
||||
"format": format
|
||||
]
|
||||
resultList.append(imageInfo)
|
||||
|
||||
let currentUploaded = uploadedCount
|
||||
lock.unlock()
|
||||
|
||||
// 进度回调
|
||||
DispatchQueue.main.async {
|
||||
progress(currentUploaded, total)
|
||||
}
|
||||
|
||||
// 全部完成
|
||||
if currentUploaded == total {
|
||||
DispatchQueue.main.async {
|
||||
success(resultList)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
lock.unlock()
|
||||
}
|
||||
semaphore.signal()
|
||||
} else {
|
||||
semaphore.signal()
|
||||
}
|
||||
}
|
||||
|
||||
// 执行上传
|
||||
QCloudCOSTransferMangerService.defaultCOSTransferManager().uploadObject(request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析上传返回的 URL(参考 UploadFile.m line 217-223)
|
||||
/// - Parameters:
|
||||
/// - location: QCloud 返回的原始 URL
|
||||
/// - customDomain: 自定义域名
|
||||
/// - Returns: 解析后的 URL
|
||||
private func parseUploadURL(_ location: String, customDomain: String) -> String {
|
||||
let components = location.components(separatedBy: ".com/")
|
||||
if components.count == 2 {
|
||||
return "\(customDomain)/\(components[1])"
|
||||
}
|
||||
return location
|
||||
}
|
||||
}
|
||||
51
YuMi/E-P/Common/EPProgressHUD.swift
Normal file
51
YuMi/E-P/Common/EPProgressHUD.swift
Normal file
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// EPProgressHUD.swift
|
||||
// YuMi
|
||||
//
|
||||
// Created by AI on 2025-10-11.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Foundation
|
||||
|
||||
/// 带进度的 Loading 组件(基于 MBProgressHUD)
|
||||
@objc class EPProgressHUD: NSObject {
|
||||
|
||||
private static var currentHUD: MBProgressHUD?
|
||||
|
||||
/// 显示上传进度
|
||||
/// - Parameters:
|
||||
/// - uploaded: 已上传数量
|
||||
/// - total: 总数量
|
||||
@objc static func showProgress(_ uploaded: Int, total: Int) {
|
||||
DispatchQueue.main.async {
|
||||
guard let window = UIApplication.shared.keyWindow else { return }
|
||||
|
||||
if let hud = currentHUD {
|
||||
// 更新现有 HUD
|
||||
hud.label.text = "上传中 \(uploaded)/\(total)"
|
||||
hud.progress = Float(uploaded) / Float(total)
|
||||
} else {
|
||||
// 创建新 HUD
|
||||
let hud = MBProgressHUD.showAdded(to: window, animated: true)
|
||||
hud.mode = .determinateHorizontalBar
|
||||
hud.label.text = "上传中 \(uploaded)/\(total)"
|
||||
hud.progress = Float(uploaded) / Float(total)
|
||||
hud.removeFromSuperViewOnHide = true
|
||||
currentHUD = hud
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 关闭 HUD
|
||||
@objc static func dismiss() {
|
||||
DispatchQueue.main.async {
|
||||
guard let window = UIApplication.shared.keyWindow,
|
||||
let hud = currentHUD else { return }
|
||||
|
||||
hud.hide(animated: true)
|
||||
currentHUD = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
56
YuMi/E-P/Common/EPQCloudConfig.swift
Normal file
56
YuMi/E-P/Common/EPQCloudConfig.swift
Normal file
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// EPQCloudConfig.swift
|
||||
// YuMi
|
||||
//
|
||||
// Created by AI on 2025-10-11.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// QCloud 配置数据模型(对应 UploadFileModel)
|
||||
struct EPQCloudConfig {
|
||||
let secretId: String
|
||||
let secretKey: String
|
||||
let sessionToken: String
|
||||
let bucket: String
|
||||
let region: String
|
||||
let customDomain: String
|
||||
let startTime: Int64
|
||||
let expireTime: Int64
|
||||
let appId: String
|
||||
let accelerate: Int
|
||||
|
||||
/// 从 API 返回的 dictionary 初始化
|
||||
/// API: GET tencent/cos/getToken
|
||||
init?(dictionary: [String: Any]) {
|
||||
// 必填字段检查
|
||||
guard let secretId = dictionary["secretId"] as? String,
|
||||
let secretKey = dictionary["secretKey"] as? String,
|
||||
let sessionToken = dictionary["sessionToken"] as? String,
|
||||
let bucket = dictionary["bucket"] as? String,
|
||||
let region = dictionary["region"] as? String,
|
||||
let customDomain = dictionary["customDomain"] as? String,
|
||||
let appId = dictionary["appId"] as? String else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.secretId = secretId
|
||||
self.secretKey = secretKey
|
||||
self.sessionToken = sessionToken
|
||||
self.bucket = bucket
|
||||
self.region = region
|
||||
self.customDomain = customDomain
|
||||
self.appId = appId
|
||||
|
||||
// 可选字段使用默认值
|
||||
self.startTime = (dictionary["startTime"] as? Int64) ?? 0
|
||||
self.expireTime = (dictionary["expireTime"] as? Int64) ?? 0
|
||||
self.accelerate = (dictionary["accelerate"] as? Int) ?? 0
|
||||
}
|
||||
|
||||
/// 检查配置是否过期
|
||||
var isExpired: Bool {
|
||||
return Date().timeIntervalSince1970 > Double(expireTime)
|
||||
}
|
||||
}
|
||||
|
||||
253
YuMi/E-P/Common/EPSDKManager.swift
Normal file
253
YuMi/E-P/Common/EPSDKManager.swift
Normal file
@@ -0,0 +1,253 @@
|
||||
//
|
||||
// EPSDKManager.swift
|
||||
// YuMi
|
||||
//
|
||||
// Created by AI on 2025-10-11.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// 第三方 SDK 统一管理器(单例)
|
||||
/// 统一入口:对外提供所有 SDK 能力
|
||||
/// 内部管理:QCloud 初始化、配置、上传等
|
||||
@objc class EPSDKManager: NSObject, QCloudSignatureProvider, QCloudCredentailFenceQueueDelegate {
|
||||
|
||||
// MARK: - Singleton
|
||||
|
||||
@objc static let shared = EPSDKManager()
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// QCloud 配置缓存
|
||||
private var qcloudConfig: EPQCloudConfig?
|
||||
|
||||
// QCloud 初始化状态
|
||||
private var isQCloudInitializing = false
|
||||
|
||||
// QCloud 初始化回调队列
|
||||
private var qcloudInitCallbacks: [(Bool, String?) -> Void] = []
|
||||
|
||||
// QCloud 凭证队列
|
||||
private var credentialFenceQueue: QCloudCredentailFenceQueue?
|
||||
|
||||
// 线程安全锁
|
||||
private let lock = NSLock()
|
||||
|
||||
// 内部图片上传器
|
||||
private let uploader = EPImageUploader()
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
private override init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
// MARK: - Public API (对外统一入口)
|
||||
|
||||
/// 批量上传图片(统一入口)
|
||||
/// - Parameters:
|
||||
/// - images: 要上传的图片数组
|
||||
/// - progress: 进度回调 (已上传数, 总数)
|
||||
/// - success: 成功回调,返回图片信息数组
|
||||
/// - failure: 失败回调
|
||||
@objc func uploadImages(
|
||||
_ images: [UIImage],
|
||||
progress: @escaping (Int, Int) -> Void,
|
||||
success: @escaping ([[String: Any]]) -> Void,
|
||||
failure: @escaping (String) -> Void
|
||||
) {
|
||||
guard !images.isEmpty else {
|
||||
success([])
|
||||
return
|
||||
}
|
||||
|
||||
// 确保 QCloud 已就绪
|
||||
ensureQCloudReady { [weak self] isReady, errorMsg in
|
||||
guard let self = self, isReady else {
|
||||
DispatchQueue.main.async {
|
||||
failure(errorMsg ?? "QCloud 初始化失败")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 委托给内部 uploader 执行
|
||||
self.uploader.performBatchUpload(
|
||||
images,
|
||||
bucket: self.qcloudConfig?.bucket ?? "",
|
||||
customDomain: self.qcloudConfig?.customDomain ?? "",
|
||||
progress: progress,
|
||||
success: success,
|
||||
failure: failure
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// 检查 QCloud 是否已就绪
|
||||
/// - Returns: true 表示已初始化且未过期
|
||||
@objc func isQCloudReady() -> Bool {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
|
||||
guard let config = qcloudConfig else {
|
||||
return false
|
||||
}
|
||||
return !config.isExpired
|
||||
}
|
||||
|
||||
// MARK: - Internal Methods
|
||||
|
||||
/// 确保 QCloud 已就绪(自动初始化)
|
||||
private func ensureQCloudReady(completion: @escaping (Bool, String?) -> Void) {
|
||||
if isQCloudReady() {
|
||||
completion(true, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 未初始化或已过期,重新初始化
|
||||
initializeQCloud(completion: completion)
|
||||
}
|
||||
|
||||
/// 初始化 QCloud(获取 Token 并配置 SDK)
|
||||
private func initializeQCloud(completion: @escaping (Bool, String?) -> Void) {
|
||||
lock.lock()
|
||||
|
||||
// 如果正在初始化,加入回调队列
|
||||
if isQCloudInitializing {
|
||||
qcloudInitCallbacks.append(completion)
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// 如果已初始化且未过期,直接返回
|
||||
if let config = qcloudConfig, !config.isExpired {
|
||||
lock.unlock()
|
||||
completion(true, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 开始初始化
|
||||
isQCloudInitializing = true
|
||||
qcloudInitCallbacks.append(completion)
|
||||
lock.unlock()
|
||||
|
||||
// 调用 API 获取 QCloud Token
|
||||
// API: GET tencent/cos/getToken
|
||||
Api.getQCloudInfo { [weak self] (data, code, msg) in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.lock.lock()
|
||||
|
||||
if code == 200,
|
||||
let dict = data?.data as? [String: Any],
|
||||
let config = EPQCloudConfig(dictionary: dict) {
|
||||
|
||||
// 保存配置
|
||||
self.qcloudConfig = config
|
||||
|
||||
// 配置 QCloud SDK
|
||||
self.configureQCloudSDK(with: config)
|
||||
|
||||
// 初始化完成
|
||||
self.isQCloudInitializing = false
|
||||
let callbacks = self.qcloudInitCallbacks
|
||||
self.qcloudInitCallbacks.removeAll()
|
||||
self.lock.unlock()
|
||||
|
||||
// 短暂延迟确保 SDK 配置完成
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
||||
callbacks.forEach { $0(true, nil) }
|
||||
}
|
||||
} else {
|
||||
// 初始化失败
|
||||
self.isQCloudInitializing = false
|
||||
let callbacks = self.qcloudInitCallbacks
|
||||
self.qcloudInitCallbacks.removeAll()
|
||||
self.lock.unlock()
|
||||
|
||||
let errorMsg = msg ?? "获取 QCloud 配置失败"
|
||||
DispatchQueue.main.async {
|
||||
callbacks.forEach { $0(false, errorMsg) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 配置 QCloud SDK(参考 UploadFile.m line 42-64)
|
||||
private func configureQCloudSDK(with config: EPQCloudConfig) {
|
||||
let configuration = QCloudServiceConfiguration()
|
||||
configuration.appID = config.appId
|
||||
|
||||
let endpoint = QCloudCOSXMLEndPoint()
|
||||
endpoint.regionName = config.region
|
||||
endpoint.useHTTPS = true
|
||||
|
||||
// 全球加速(参考 UploadFile.m line 56-59)
|
||||
if config.accelerate == 1 {
|
||||
endpoint.suffix = "cos.accelerate.myqcloud.com"
|
||||
}
|
||||
|
||||
configuration.endpoint = endpoint
|
||||
configuration.signatureProvider = self
|
||||
|
||||
// 注册 COS 服务
|
||||
QCloudCOSXMLService.registerDefaultCOSXML(with: configuration)
|
||||
QCloudCOSTransferMangerService.registerDefaultCOSTransferManger(with: configuration)
|
||||
|
||||
// 初始化凭证队列
|
||||
credentialFenceQueue = QCloudCredentailFenceQueue()
|
||||
credentialFenceQueue?.delegate = self
|
||||
}
|
||||
|
||||
// MARK: - QCloudSignatureProvider Protocol
|
||||
|
||||
/// 提供签名(参考 UploadFile.m line 67-104)
|
||||
func signature(
|
||||
with fields: QCloudSignatureFields,
|
||||
request: QCloudBizHTTPRequest,
|
||||
urlRequest: NSMutableURLRequest,
|
||||
compelete: @escaping QCloudHTTPAuthentationContinueBlock
|
||||
) {
|
||||
guard let config = qcloudConfig else {
|
||||
let error = NSError(domain: "com.yumi.qcloud", code: -1,
|
||||
userInfo: [NSLocalizedDescriptionKey: "QCloud 配置未初始化"])
|
||||
compelete(nil, error)
|
||||
return
|
||||
}
|
||||
|
||||
let credential = QCloudCredential()
|
||||
credential.secretID = config.secretId
|
||||
credential.secretKey = config.secretKey
|
||||
credential.token = config.sessionToken
|
||||
credential.startDate = Date(timeIntervalSince1970: TimeInterval(config.startTime))
|
||||
credential.expirationDate = Date(timeIntervalSince1970: TimeInterval(config.expireTime))
|
||||
|
||||
let creator = QCloudAuthentationV5Creator(credential: credential)
|
||||
let signature = creator?.signature(forData: urlRequest)
|
||||
compelete(signature, nil)
|
||||
}
|
||||
|
||||
// MARK: - QCloudCredentailFenceQueueDelegate Protocol
|
||||
|
||||
/// 管理凭证(参考 UploadFile.m line 107-133)
|
||||
func fenceQueue(
|
||||
_ queue: QCloudCredentailFenceQueue,
|
||||
requestCreatorWithContinue continueBlock: @escaping QCloudCredentailFenceQueueContinue
|
||||
) {
|
||||
guard let config = qcloudConfig else {
|
||||
let error = NSError(domain: "com.yumi.qcloud", code: -1,
|
||||
userInfo: [NSLocalizedDescriptionKey: "QCloud 配置未初始化"])
|
||||
continueBlock(nil, error)
|
||||
return
|
||||
}
|
||||
|
||||
let credential = QCloudCredential()
|
||||
credential.secretID = config.secretId
|
||||
credential.secretKey = config.secretKey
|
||||
credential.token = config.sessionToken
|
||||
credential.startDate = Date(timeIntervalSince1970: TimeInterval(config.startTime))
|
||||
credential.expirationDate = Date(timeIntervalSince1970: TimeInterval(config.expireTime))
|
||||
|
||||
let creator = QCloudAuthentationV5Creator(credential: credential)
|
||||
continueBlock(creator, nil)
|
||||
}
|
||||
}
|
||||
@@ -8,38 +8,29 @@
|
||||
|
||||
#import "EPMineViewController.h"
|
||||
#import "EPMineHeaderView.h"
|
||||
#import "EPMomentCell.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import "Api+Moments.h"
|
||||
#import "EPMomentListView.h"
|
||||
#import "EPMineAPIHelper.h"
|
||||
#import "AccountInfoStorage.h"
|
||||
#import "UserInfoModel.h"
|
||||
#import "MomentsInfoModel.h"
|
||||
#import <MJExtension/MJExtension.h>
|
||||
|
||||
@interface EPMineViewController () <UITableViewDelegate, UITableViewDataSource>
|
||||
@interface EPMineViewController ()
|
||||
|
||||
// MARK: - UI Components
|
||||
|
||||
/// 主列表(显示用户动态)
|
||||
@property (nonatomic, strong) UITableView *tableView;
|
||||
/// 动态列表视图(复用 EPMomentListView)
|
||||
@property (nonatomic, strong) EPMomentListView *momentListView;
|
||||
|
||||
/// 顶部个人信息卡片
|
||||
@property (nonatomic, strong) EPMineHeaderView *headerView;
|
||||
|
||||
// MARK: - Data
|
||||
|
||||
/// 用户动态数据源
|
||||
@property (nonatomic, strong) NSMutableArray<MomentsInfoModel *> *momentsData;
|
||||
|
||||
/// 当前页码
|
||||
@property (nonatomic, assign) NSInteger currentPage;
|
||||
|
||||
/// 是否正在加载
|
||||
@property (nonatomic, assign) BOOL isLoading;
|
||||
|
||||
/// 用户信息模型
|
||||
@property (nonatomic, strong) UserInfoModel *userInfo;
|
||||
|
||||
/// API Helper
|
||||
@property (nonatomic, strong) EPMineAPIHelper *apiHelper;
|
||||
|
||||
@end
|
||||
|
||||
@implementation EPMineViewController
|
||||
@@ -49,13 +40,7 @@
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
self.momentsData = [NSMutableArray array];
|
||||
self.currentPage = 1;
|
||||
self.isLoading = NO;
|
||||
|
||||
[self setupUI];
|
||||
[self loadUserInfo];
|
||||
[self loadUserMoments];
|
||||
|
||||
NSLog(@"[EPMineViewController] 个人主页加载完成");
|
||||
}
|
||||
@@ -63,27 +48,17 @@
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
// 注意:当前 ViewController 没有包装在 NavigationController 中
|
||||
// 如果未来需要导航栏,应该在 TabBarController 中包装 UINavigationController
|
||||
// 隐藏导航栏
|
||||
[self.navigationController setNavigationBarHidden:YES animated:animated];
|
||||
|
||||
// 每次显示时加载最新数据
|
||||
[self loadUserDetailInfo];
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
- (void)setupGradientBackground {
|
||||
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
|
||||
gradientLayer.frame = self.view.bounds;
|
||||
gradientLayer.colors = @[
|
||||
(id)[UIColor colorWithRed:0.3 green:0.2 blue:0.6 alpha:1.0].CGColor, // 深紫 #4C3399
|
||||
(id)[UIColor colorWithRed:0.2 green:0.3 blue:0.8 alpha:1.0].CGColor // 蓝 #3366CC
|
||||
];
|
||||
gradientLayer.startPoint = CGPointMake(0, 0);
|
||||
gradientLayer.endPoint = CGPointMake(1, 1);
|
||||
[self.view.layer insertSublayer:gradientLayer atIndex:0];
|
||||
}
|
||||
|
||||
- (void)setupUI {
|
||||
// 先设置纯色背景作为兜底,避免白色闪烁
|
||||
self.view.backgroundColor = [UIColor colorWithRed:0.95 green:0.95 blue:0.97 alpha:1.0];
|
||||
self.view.backgroundColor = [UIColor clearColor];
|
||||
|
||||
UIImageView *bgImageView = [[UIImageView alloc] initWithImage:kImage(@"vc_bg")];
|
||||
bgImageView.contentMode = UIViewContentModeScaleAspectFill;
|
||||
@@ -94,7 +69,7 @@
|
||||
}];
|
||||
|
||||
[self setupHeaderView];
|
||||
[self setupTableView];
|
||||
[self setupMomentListView];
|
||||
|
||||
NSLog(@"[EPMineViewController] UI 设置完成");
|
||||
}
|
||||
@@ -105,140 +80,56 @@
|
||||
[self.view addSubview:self.headerView];
|
||||
|
||||
[self.headerView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop).offset(20);
|
||||
make.left.right.equalTo(self.view);
|
||||
make.top.equalTo(self.view).offset(20);
|
||||
make.leading.trailing.equalTo(self.view);
|
||||
make.height.equalTo(@300);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setupTableView {
|
||||
self.tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
|
||||
self.tableView.delegate = self;
|
||||
self.tableView.dataSource = self;
|
||||
self.tableView.backgroundColor = [UIColor clearColor];
|
||||
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
self.tableView.showsVerticalScrollIndicator = NO;
|
||||
- (void)setupMomentListView {
|
||||
[self.view addSubview:self.momentListView];
|
||||
|
||||
// 注册动态 cell(复用 EPMomentCell)
|
||||
[self.tableView registerClass:[EPMomentCell class] forCellReuseIdentifier:@"EPMomentCell"];
|
||||
|
||||
[self.view addSubview:self.tableView];
|
||||
|
||||
[self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
[self.momentListView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.headerView.mas_bottom).offset(10);
|
||||
make.left.right.equalTo(self.view);
|
||||
make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom);
|
||||
make.leading.trailing.bottom.equalTo(self.view);
|
||||
}];
|
||||
|
||||
// 添加下拉刷新
|
||||
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
|
||||
[refreshControl addTarget:self action:@selector(refreshData) forControlEvents:UIControlEventValueChanged];
|
||||
self.tableView.refreshControl = refreshControl;
|
||||
}
|
||||
|
||||
// MARK: - Data Loading
|
||||
|
||||
- (void)loadUserInfo {
|
||||
- (void)loadUserDetailInfo {
|
||||
NSString *uid = [[AccountInfoStorage instance] getUid];
|
||||
if (!uid.length) {
|
||||
NSLog(@"[EPMineViewController] 未登录,无法获取用户信息");
|
||||
return;
|
||||
}
|
||||
|
||||
// 调用真实 API 获取用户信息
|
||||
[Api getUserInfo:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
|
||||
if (code == 200 && data.data) {
|
||||
self.userInfo = [UserInfoModel mj_objectWithKeyValues:data.data];
|
||||
|
||||
// 更新头部视图
|
||||
NSDictionary *userInfoDict = @{
|
||||
@"nickname": self.userInfo.nick ?: @"未设置昵称",
|
||||
@"avatar": self.userInfo.avatar ?: @"",
|
||||
@"uid": self.userInfo.uid > 0 ? @(self.userInfo.uid).stringValue : @"",
|
||||
@"followers": @(self.userInfo.fansNum),
|
||||
@"following": @(self.userInfo.followNum),
|
||||
};
|
||||
|
||||
[self.headerView updateWithUserInfo:userInfoDict];
|
||||
NSLog(@"[EPMineViewController] 用户信息加载成功: %@", self.userInfo.nick);
|
||||
} else {
|
||||
NSLog(@"[EPMineViewController] 用户信息加载失败: %@", msg);
|
||||
}
|
||||
} uid:uid];
|
||||
}
|
||||
|
||||
- (void)refreshUserInfo {
|
||||
[self loadUserInfo];
|
||||
}
|
||||
|
||||
- (void)loadUserMoments {
|
||||
if (self.isLoading) return;
|
||||
|
||||
NSString *uid = [[AccountInfoStorage instance] getUid];
|
||||
if (!uid.length) {
|
||||
NSLog(@"[EPMineViewController] 未登录,无法获取用户动态");
|
||||
return;
|
||||
}
|
||||
|
||||
self.isLoading = YES;
|
||||
NSString *page = [NSString stringWithFormat:@"%ld", (long)self.currentPage];
|
||||
|
||||
// 调用获取用户动态的 API(这里先用通用的动态列表 API)
|
||||
[Api momentsRecommendList:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
|
||||
self.isLoading = NO;
|
||||
[self.refreshControl endRefreshing];
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[self.apiHelper getUserDetailInfoWithUid:uid completion:^(UserInfoModel * _Nullable userInfo) {
|
||||
__strong typeof(weakSelf) self = weakSelf;
|
||||
self.userInfo = userInfo;
|
||||
|
||||
if (code == 200 && data.data) {
|
||||
NSArray *list = [MomentsInfoModel mj_objectArrayWithKeyValuesArray:data.data];
|
||||
if (list.count > 0) {
|
||||
[self.momentsData addObjectsFromArray:list];
|
||||
self.currentPage++;
|
||||
[self.tableView reloadData];
|
||||
NSLog(@"[EPMineViewController] 用户动态加载成功,新增 %lu 条", (unsigned long)list.count);
|
||||
}
|
||||
} else {
|
||||
NSLog(@"[EPMineViewController] 用户动态加载失败: %@", msg);
|
||||
}
|
||||
} page:page pageSize:@"10" types:@"0,2"];
|
||||
}
|
||||
|
||||
- (void)refreshData {
|
||||
self.currentPage = 1;
|
||||
[self.momentsData removeAllObjects];
|
||||
|
||||
// 手动下拉刷新时才更新用户信息
|
||||
[self loadUserInfo];
|
||||
[self loadUserMoments];
|
||||
}
|
||||
|
||||
// MARK: - UITableView DataSource
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
return self.momentsData.count;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
EPMomentCell *cell = [tableView dequeueReusableCellWithIdentifier:@"EPMomentCell" forIndexPath:indexPath];
|
||||
cell.backgroundColor = [UIColor clearColor];
|
||||
|
||||
if (indexPath.row < self.momentsData.count) {
|
||||
[cell configureWithModel:self.momentsData[indexPath.row]];
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
return 200; // 根据实际内容调整
|
||||
}
|
||||
|
||||
// MARK: - UITableView Delegate
|
||||
|
||||
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
// 滚动到底部自动加载更多
|
||||
if (indexPath.row == self.momentsData.count - 1 && !self.isLoading) {
|
||||
[self loadUserMoments];
|
||||
}
|
||||
// 更新头部视图
|
||||
NSDictionary *userInfoDict = @{
|
||||
@"nickname": userInfo.nick ?: @"未设置昵称",
|
||||
@"avatar": userInfo.avatar ?: @"",
|
||||
@"uid": userInfo.uid > 0 ? @(userInfo.uid).stringValue : @"",
|
||||
@"followers": @(userInfo.fansNum),
|
||||
@"following": @(userInfo.followNum),
|
||||
};
|
||||
[self.headerView updateWithUserInfo:userInfoDict];
|
||||
|
||||
// 使用本地数组模式显示用户动态
|
||||
[self.momentListView loadWithDynamicInfo:userInfo.dynamicInfo refreshCallback:^{
|
||||
[self loadUserDetailInfo];
|
||||
}];
|
||||
|
||||
NSLog(@"[EPMineViewController] 用户详情加载成功: %@ (动态数: %lu)",
|
||||
userInfo.nick, (unsigned long)userInfo.dynamicInfo.count);
|
||||
|
||||
} failure:^(NSInteger code, NSString * _Nullable msg) {
|
||||
NSLog(@"[EPMineViewController] 用户详情加载失败: code=%ld, msg=%@", (long)code, msg);
|
||||
}];
|
||||
}
|
||||
|
||||
// MARK: - Lazy Loading
|
||||
@@ -250,8 +141,25 @@
|
||||
return _headerView;
|
||||
}
|
||||
|
||||
- (UIRefreshControl *)refreshControl {
|
||||
return self.tableView.refreshControl;
|
||||
- (EPMomentListView *)momentListView {
|
||||
if (!_momentListView) {
|
||||
_momentListView = [[EPMomentListView alloc] initWithFrame:CGRectZero];
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
_momentListView.onSelectMoment = ^(NSInteger index) {
|
||||
__strong typeof(weakSelf) self = weakSelf;
|
||||
NSLog(@"[EPMineViewController] 点击了第 %ld 条动态", (long)index);
|
||||
// TODO: 跳转到动态详情页
|
||||
};
|
||||
}
|
||||
return _momentListView;
|
||||
}
|
||||
|
||||
- (EPMineAPIHelper *)apiHelper {
|
||||
if (!_apiHelper) {
|
||||
_apiHelper = [[EPMineAPIHelper alloc] init];
|
||||
}
|
||||
return _apiHelper;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
30
YuMi/E-P/NewMine/Services/EPMineAPIHelper.h
Normal file
30
YuMi/E-P/NewMine/Services/EPMineAPIHelper.h
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// EPMineAPIHelper.h
|
||||
// YuMi
|
||||
//
|
||||
// Created by AI on 2025-10-10.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class UserInfoModel;
|
||||
|
||||
/// 封装用户信息相关 API
|
||||
@interface EPMineAPIHelper : NSObject
|
||||
|
||||
/// 获取用户基础信息
|
||||
- (void)getUserInfoWithUid:(NSString *)uid
|
||||
completion:(void (^)(UserInfoModel * _Nullable userInfo))completion
|
||||
failure:(void (^)(NSInteger code, NSString * _Nullable msg))failure;
|
||||
|
||||
/// 获取用户详细信息(包含 dynamicInfo)
|
||||
- (void)getUserDetailInfoWithUid:(NSString *)uid
|
||||
completion:(void (^)(UserInfoModel * _Nullable userInfo))completion
|
||||
failure:(void (^)(NSInteger code, NSString * _Nullable msg))failure;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
42
YuMi/E-P/NewMine/Services/EPMineAPIHelper.m
Normal file
42
YuMi/E-P/NewMine/Services/EPMineAPIHelper.m
Normal file
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// EPMineAPIHelper.m
|
||||
// YuMi
|
||||
//
|
||||
// Created by AI on 2025-10-10.
|
||||
//
|
||||
|
||||
#import "EPMineAPIHelper.h"
|
||||
#import "Api+Mine.h"
|
||||
#import "UserInfoModel.h"
|
||||
#import "BaseModel.h"
|
||||
|
||||
@implementation EPMineAPIHelper
|
||||
|
||||
- (void)getUserInfoWithUid:(NSString *)uid
|
||||
completion:(void (^)(UserInfoModel * _Nullable userInfo))completion
|
||||
failure:(void (^)(NSInteger code, NSString * _Nullable msg))failure {
|
||||
[Api getUserInfo:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
|
||||
if (code == 200 && data.data) {
|
||||
UserInfoModel *userInfo = [UserInfoModel modelWithDictionary:data.data];
|
||||
if (completion) completion(userInfo);
|
||||
} else {
|
||||
if (failure) failure(code, msg);
|
||||
}
|
||||
} uid:uid];
|
||||
}
|
||||
|
||||
- (void)getUserDetailInfoWithUid:(NSString *)uid
|
||||
completion:(void (^)(UserInfoModel * _Nullable userInfo))completion
|
||||
failure:(void (^)(NSInteger code, NSString * _Nullable msg))failure {
|
||||
[Api userDetailInfoCompletion:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
|
||||
if (code == 200 && data.data) {
|
||||
UserInfoModel *userInfo = [UserInfoModel modelWithDictionary:data.data];
|
||||
if (completion) completion(userInfo);
|
||||
} else {
|
||||
if (failure) failure(code, msg);
|
||||
}
|
||||
} uid:uid page:@"1" pageSize:@"20"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
|
||||
[self.settingsButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self).offset(50);
|
||||
make.right.equalTo(self).offset(-20);
|
||||
make.trailing.equalTo(self).offset(-20);
|
||||
make.size.mas_equalTo(CGSizeMake(40, 40));
|
||||
}];
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// 发布成功通知
|
||||
extern NSString *const EPMomentPublishSuccessNotification;
|
||||
|
||||
/// EP 版:图文发布页面
|
||||
@interface EPMomentPublishViewController : UIViewController
|
||||
|
||||
|
||||
@@ -5,11 +5,20 @@
|
||||
// Created by AI on 2025-10-10.
|
||||
//
|
||||
|
||||
// NOTE: 话题选择功能未实现
|
||||
// 旧版本 XPMonentsPublishViewController 包含话题选择 UI (addTopicView)
|
||||
// 但实际业务中话题功能使用率低,新版本暂不实现
|
||||
// 如需实现参考: YuMi/Modules/YMMonents/View/XPMonentsPublishTopicView
|
||||
|
||||
#import "EPMomentPublishViewController.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import <TZImagePickerController/TZImagePickerController.h>
|
||||
#import "DJDKMIMOMColor.h"
|
||||
#import "SZTextView.h"
|
||||
#import "YuMi-Swift.h"
|
||||
|
||||
// 发布成功通知
|
||||
NSString *const EPMomentPublishSuccessNotification = @"EPMomentPublishSuccessNotification";
|
||||
|
||||
@interface EPMomentPublishViewController () <UICollectionViewDataSource, UICollectionViewDelegate, TZImagePickerControllerDelegate, UITextViewDelegate>
|
||||
|
||||
@@ -102,8 +111,57 @@
|
||||
}
|
||||
|
||||
- (void)onPublish {
|
||||
// TODO: 挂接实际发布逻辑
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
[self.view endEditing:YES];
|
||||
|
||||
// 验证:文本或图片至少有一项
|
||||
if (self.textView.text.length == 0 && self.images.count == 0) {
|
||||
// TODO: 显示错误提示 "请输入内容或选择图片"
|
||||
NSLog(@"请输入内容或选择图片");
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建 Swift API Helper
|
||||
EPMomentAPISwiftHelper *apiHelper = [[EPMomentAPISwiftHelper alloc] init];
|
||||
|
||||
if (self.images.count > 0) {
|
||||
// 有图片:上传后发布(统一入口)
|
||||
[[EPSDKManager shared] uploadImages:self.images
|
||||
progress:^(NSInteger uploaded, NSInteger total) {
|
||||
[EPProgressHUD showProgress:uploaded total:total];
|
||||
}
|
||||
success:^(NSArray<NSDictionary *> *resList) {
|
||||
[EPProgressHUD dismiss];
|
||||
[apiHelper publishMomentWithType:@"2"
|
||||
content:self.textView.text ?: @""
|
||||
resList:resList
|
||||
completion:^{
|
||||
// 发送发布成功通知
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:EPMomentPublishSuccessNotification object:nil];
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
} failure:^(NSInteger code, NSString *msg) {
|
||||
// TODO: 显示错误 Toast
|
||||
NSLog(@"发布失败: %ld - %@", (long)code, msg);
|
||||
}];
|
||||
}
|
||||
failure:^(NSString *errorMsg) {
|
||||
[EPProgressHUD dismiss];
|
||||
// TODO: 显示错误 Toast
|
||||
NSLog(@"上传失败: %@", errorMsg);
|
||||
}];
|
||||
} else {
|
||||
// 纯文本:直接发布
|
||||
[apiHelper publishMomentWithType:@"0"
|
||||
content:self.textView.text
|
||||
resList:@[]
|
||||
completion:^{
|
||||
// 发送发布成功通知
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:EPMomentPublishSuccessNotification object:nil];
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
} failure:^(NSInteger code, NSString *msg) {
|
||||
// TODO: 显示错误 Toast
|
||||
NSLog(@"发布失败: %ld - %@", (long)code, msg);
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionView
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#import "EPMomentCell.h"
|
||||
#import "EPMomentListView.h"
|
||||
#import "EPMomentPublishViewController.h"
|
||||
#import "YUMIMacroUitls.h"
|
||||
|
||||
@interface EPMomentViewController ()
|
||||
|
||||
@@ -37,14 +38,17 @@
|
||||
[self setupUI];
|
||||
[self.listView reloadFirstPage];
|
||||
|
||||
// 监听发布成功通知
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(onMomentPublishSuccess:)
|
||||
name:EPMomentPublishSuccessNotification
|
||||
object:nil];
|
||||
|
||||
NSLog(@"[EPMomentViewController] 页面加载完成");
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
// 注意:当前 ViewController 没有包装在 NavigationController 中
|
||||
// 如果未来需要导航栏,应该在 TabBarController 中包装 UINavigationController
|
||||
}
|
||||
|
||||
// MARK: - Setup UI
|
||||
@@ -65,14 +69,13 @@
|
||||
[self.view addSubview:self.topTipLabel];
|
||||
[self.topTipLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop).offset(8);
|
||||
make.left.equalTo(self.view).offset(20);
|
||||
make.right.equalTo(self.view).offset(-20);
|
||||
make.leading.trailing.equalTo(self.view).inset(20);
|
||||
}];
|
||||
|
||||
// 列表视图
|
||||
[self.view addSubview:self.listView];
|
||||
[self.listView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.bottom.equalTo(self.view);
|
||||
make.leading.trailing.bottom.equalTo(self.view);
|
||||
make.top.equalTo(self.topTipLabel.mas_bottom).offset(8);
|
||||
}];
|
||||
|
||||
@@ -102,6 +105,15 @@
|
||||
[self presentViewController:alert animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)onMomentPublishSuccess:(NSNotification *)notification {
|
||||
NSLog(@"[EPMomentViewController] 收到发布成功通知,刷新列表");
|
||||
[self.listView reloadFirstPage];
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
// 列表点击回调由 listView 暴露
|
||||
|
||||
// MARK: - Lazy Loading
|
||||
|
||||
@@ -26,6 +26,8 @@ typedef NS_ENUM(NSInteger, EPMomentListSourceType) {
|
||||
completion:(void (^)(NSArray <MomentsInfoModel *>* _Nullable list, NSString *nextMomentID))completion
|
||||
failure:(void(^)(NSInteger code, NSString * _Nullable msg))failure;
|
||||
|
||||
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
47
YuMi/E-P/NewMoments/Services/EPMomentAPISwiftHelper.swift
Normal file
47
YuMi/E-P/NewMoments/Services/EPMomentAPISwiftHelper.swift
Normal file
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// EPMomentAPISwiftHelper.swift
|
||||
// YuMi
|
||||
//
|
||||
// Created by AI on 2025-10-11.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// 动态 API 封装(Swift 现代化版本)
|
||||
/// 与现有 OC 版本 EPMomentAPIHelper 并存,供对比评估
|
||||
@objc class EPMomentAPISwiftHelper: NSObject {
|
||||
|
||||
/// 发布动态
|
||||
/// - Parameters:
|
||||
/// - type: "0"=纯文本, "2"=图片
|
||||
/// - content: 文本内容
|
||||
/// - resList: 图片信息数组
|
||||
/// - completion: 成功回调
|
||||
/// - failure: 失败回调 (错误码, 错误信息)
|
||||
@objc func publishMoment(
|
||||
type: String,
|
||||
content: String,
|
||||
resList: [[String: Any]],
|
||||
completion: @escaping () -> Void,
|
||||
failure: @escaping (Int, String) -> Void
|
||||
) {
|
||||
guard let uid = AccountInfoStorage.instance().getUid() else {
|
||||
failure(-1, "用户未登录")
|
||||
return
|
||||
}
|
||||
|
||||
// worldId 传空字符串(话题功能不实现)
|
||||
// NOTE: 旧版本 XPMonentsPublishViewController 包含话题选择功能
|
||||
// 但实际业务中话题功能使用率低,新版本暂不实现
|
||||
// 如需实现参考: YuMi/Modules/YMMonents/View/XPMonentsPublishTopicView
|
||||
|
||||
Api.momentsPublish({ (data, code, msg) in
|
||||
if code == 200 {
|
||||
completion()
|
||||
} else {
|
||||
failure(Int(code), msg ?? "发布失败")
|
||||
}
|
||||
}, uid: uid, type: type, worldId: "", content: content, resList: resList)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,8 +70,7 @@
|
||||
// 卡片容器(圆角矩形 + 阴影)
|
||||
[self.contentView addSubview:self.cardView];
|
||||
[self.cardView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.contentView).offset(15);
|
||||
make.right.equalTo(self.contentView).offset(-15);
|
||||
make.leading.trailing.equalTo(self.contentView).inset(15);
|
||||
make.top.equalTo(self.contentView).offset(8);
|
||||
make.bottom.equalTo(self.contentView).offset(-8);
|
||||
}];
|
||||
@@ -79,7 +78,7 @@
|
||||
// 头像(圆角矩形,不是圆形!)
|
||||
[self.cardView addSubview:self.avatarImageView];
|
||||
[self.avatarImageView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.cardView).offset(15);
|
||||
make.leading.equalTo(self.cardView).offset(15);
|
||||
make.top.equalTo(self.cardView).offset(15);
|
||||
make.size.mas_equalTo(CGSizeMake(40, 40));
|
||||
}];
|
||||
@@ -87,39 +86,38 @@
|
||||
// 用户名
|
||||
[self.cardView addSubview:self.nameLabel];
|
||||
[self.nameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.avatarImageView.mas_right).offset(10);
|
||||
make.leading.equalTo(self.avatarImageView.mas_trailing).offset(10);
|
||||
make.top.equalTo(self.avatarImageView);
|
||||
make.right.equalTo(self.cardView).offset(-15);
|
||||
make.trailing.equalTo(self.cardView).offset(-15);
|
||||
}];
|
||||
|
||||
// 时间
|
||||
[self.cardView addSubview:self.timeLabel];
|
||||
[self.timeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.nameLabel);
|
||||
make.leading.equalTo(self.nameLabel);
|
||||
make.bottom.equalTo(self.avatarImageView);
|
||||
make.right.equalTo(self.cardView).offset(-15);
|
||||
make.trailing.equalTo(self.cardView).offset(-15);
|
||||
}];
|
||||
|
||||
// 内容
|
||||
[self.cardView addSubview:self.contentLabel];
|
||||
[self.contentLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.cardView).offset(15);
|
||||
make.right.equalTo(self.cardView).offset(-15);
|
||||
make.leading.trailing.equalTo(self.cardView).inset(15);
|
||||
make.top.equalTo(self.avatarImageView.mas_bottom).offset(12);
|
||||
}];
|
||||
|
||||
// 图片九宫格
|
||||
[self.cardView addSubview:self.imagesContainer];
|
||||
[self.imagesContainer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.cardView).offset(15);
|
||||
make.right.equalTo(self.cardView).offset(-15);
|
||||
make.leading.trailing.equalTo(self.cardView).inset(15);
|
||||
make.top.equalTo(self.contentLabel.mas_bottom).offset(12);
|
||||
make.height.mas_equalTo(0); // 初始高度为0,renderImages 时会 remakeConstraints
|
||||
}];
|
||||
|
||||
// 底部操作栏
|
||||
[self.cardView addSubview:self.actionBar];
|
||||
[self.actionBar mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.equalTo(self.cardView);
|
||||
make.leading.trailing.equalTo(self.cardView);
|
||||
make.top.equalTo(self.imagesContainer.mas_bottom).offset(12);
|
||||
make.height.mas_equalTo(50);
|
||||
make.bottom.equalTo(self.cardView).offset(-8);
|
||||
@@ -128,7 +126,7 @@
|
||||
// 点赞按钮
|
||||
[self.actionBar addSubview:self.likeButton];
|
||||
[self.likeButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.actionBar).offset(15);
|
||||
make.leading.equalTo(self.actionBar).offset(15);
|
||||
make.centerY.equalTo(self.actionBar);
|
||||
make.width.mas_greaterThanOrEqualTo(60);
|
||||
}];
|
||||
@@ -178,8 +176,7 @@
|
||||
[self.imageViews removeAllObjects];
|
||||
if (resList.count == 0) {
|
||||
[self.imagesContainer mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.cardView).offset(15);
|
||||
make.right.equalTo(self.cardView).offset(-15);
|
||||
make.leading.trailing.equalTo(self.cardView).inset(15);
|
||||
make.top.equalTo(self.contentLabel.mas_bottom).offset(0);
|
||||
make.height.mas_equalTo(0);
|
||||
}];
|
||||
@@ -203,7 +200,7 @@
|
||||
NSInteger row = i / columns;
|
||||
NSInteger col = i % columns;
|
||||
[iv mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.imagesContainer).offset((itemW + spacing) * col);
|
||||
make.leading.equalTo(self.imagesContainer).offset((itemW + spacing) * col);
|
||||
make.top.equalTo(self.imagesContainer).offset((itemW + spacing) * row);
|
||||
make.size.mas_equalTo(CGSizeMake(itemW, itemW));
|
||||
}];
|
||||
@@ -221,8 +218,7 @@
|
||||
NSInteger rows = ((MIN(resList.count, 9) - 1) / columns) + 1;
|
||||
CGFloat height = rows * itemW + (rows - 1) * spacing;
|
||||
[self.imagesContainer mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.cardView).offset(15);
|
||||
make.right.equalTo(self.cardView).offset(-15);
|
||||
make.leading.trailing.equalTo(self.cardView).inset(15);
|
||||
make.top.equalTo(self.contentLabel.mas_bottom).offset(12);
|
||||
make.height.mas_equalTo(height);
|
||||
}];
|
||||
|
||||
@@ -27,6 +27,12 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/// 重新加载(刷新到第一页)
|
||||
- (void)reloadFirstPage;
|
||||
|
||||
/// 使用本地数组模式显示动态(禁用分页加载)
|
||||
/// @param dynamicInfo 本地动态数组
|
||||
/// @param refreshCallback 下拉刷新回调(由外部重新获取数据)
|
||||
- (void)loadWithDynamicInfo:(NSArray<MomentsInfoModel *> *)dynamicInfo
|
||||
refreshCallback:(void(^)(void))refreshCallback;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
@property (nonatomic, strong) EPMomentAPIHelper *api;
|
||||
@property (nonatomic, assign) BOOL isLoading;
|
||||
@property (nonatomic, copy) NSString *nextID;
|
||||
@property (nonatomic, assign) BOOL isLocalMode;
|
||||
@property (nonatomic, copy) void (^refreshCallback)(void);
|
||||
@end
|
||||
|
||||
@implementation EPMomentListView
|
||||
@@ -44,6 +46,16 @@
|
||||
}
|
||||
|
||||
- (void)reloadFirstPage {
|
||||
if (self.isLocalMode) {
|
||||
// 本地模式:调用外部刷新回调
|
||||
if (self.refreshCallback) {
|
||||
self.refreshCallback();
|
||||
}
|
||||
[self.refreshControl endRefreshing];
|
||||
return;
|
||||
}
|
||||
|
||||
// 网络模式:重新请求第一页
|
||||
self.nextID = @"";
|
||||
[self.mutableRawList removeAllObjects];
|
||||
[self.tableView reloadData];
|
||||
@@ -51,6 +63,23 @@
|
||||
[self requestNextPage];
|
||||
}
|
||||
|
||||
- (void)loadWithDynamicInfo:(NSArray<MomentsInfoModel *> *)dynamicInfo
|
||||
refreshCallback:(void (^)(void))refreshCallback {
|
||||
self.isLocalMode = YES;
|
||||
self.refreshCallback = refreshCallback;
|
||||
|
||||
[self.mutableRawList removeAllObjects];
|
||||
if (dynamicInfo.count > 0) {
|
||||
[self.mutableRawList addObjectsFromArray:dynamicInfo];
|
||||
}
|
||||
|
||||
// 隐藏加载更多 footer
|
||||
self.tableView.mj_footer.hidden = YES;
|
||||
|
||||
[self.tableView reloadData];
|
||||
[self.refreshControl endRefreshing];
|
||||
}
|
||||
|
||||
- (void)requestNextPage {
|
||||
if (self.isLoading) return;
|
||||
self.isLoading = YES;
|
||||
@@ -119,6 +148,9 @@
|
||||
}
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
|
||||
// 本地模式下不触发加载更多
|
||||
if (self.isLocalMode) return;
|
||||
|
||||
CGFloat offsetY = scrollView.contentOffset.y;
|
||||
CGFloat contentHeight = scrollView.contentSize.height;
|
||||
CGFloat screenHeight = scrollView.frame.size.height;
|
||||
|
||||
@@ -308,32 +308,25 @@ import SnapKit
|
||||
private func setupLoggedInViewControllers() {
|
||||
// 只在 viewControllers 为空或不是正确类型时才创建
|
||||
if viewControllers?.count != 2 ||
|
||||
!(viewControllers?[0] is EPMomentViewController) ||
|
||||
!(viewControllers?[1] is EPMineViewController) {
|
||||
!(viewControllers?[0] is UINavigationController) ||
|
||||
!(viewControllers?[1] is UINavigationController) {
|
||||
|
||||
// 创建真实的 ViewController(OC 类),并使用导航控制器包裹以显示标题/右上按钮
|
||||
// 创建动态页
|
||||
let momentVC = EPMomentViewController()
|
||||
momentVC.title = "动态"
|
||||
let momentNav = UINavigationController(rootViewController: momentVC)
|
||||
momentNav.navigationBar.isTranslucent = true
|
||||
momentNav.navigationBar.setBackgroundImage(UIImage(), for: .default)
|
||||
momentNav.navigationBar.shadowImage = UIImage()
|
||||
momentNav.view.backgroundColor = .clear
|
||||
momentNav.tabBarItem = createTabBarItem(
|
||||
title: "动态",
|
||||
let momentNav = createTransparentNavigationController(
|
||||
rootViewController: momentVC,
|
||||
tabTitle: "动态",
|
||||
normalImage: "tab_moment_normal",
|
||||
selectedImage: "tab_moment_selected"
|
||||
)
|
||||
|
||||
|
||||
// 创建我的页
|
||||
let mineVC = EPMineViewController()
|
||||
mineVC.title = "我的"
|
||||
let mineNav = UINavigationController(rootViewController: mineVC)
|
||||
mineNav.navigationBar.isTranslucent = true
|
||||
mineNav.navigationBar.setBackgroundImage(UIImage(), for: .default)
|
||||
mineNav.navigationBar.shadowImage = UIImage()
|
||||
mineNav.view.backgroundColor = .clear
|
||||
mineNav.tabBarItem = createTabBarItem(
|
||||
title: "我的",
|
||||
let mineNav = createTransparentNavigationController(
|
||||
rootViewController: mineVC,
|
||||
tabTitle: "我的",
|
||||
normalImage: "tab_mine_normal",
|
||||
selectedImage: "tab_mine_selected"
|
||||
)
|
||||
@@ -344,6 +337,32 @@ import SnapKit
|
||||
|
||||
selectedIndex = 0
|
||||
}
|
||||
|
||||
/// 创建透明导航控制器(统一配置)
|
||||
/// - Parameters:
|
||||
/// - rootViewController: 根视图控制器
|
||||
/// - tabTitle: TabBar 标题
|
||||
/// - normalImage: 未选中图标
|
||||
/// - selectedImage: 选中图标
|
||||
/// - Returns: 配置好的 UINavigationController
|
||||
private func createTransparentNavigationController(
|
||||
rootViewController: UIViewController,
|
||||
tabTitle: String,
|
||||
normalImage: String,
|
||||
selectedImage: String
|
||||
) -> UINavigationController {
|
||||
let nav = UINavigationController(rootViewController: rootViewController)
|
||||
nav.navigationBar.isTranslucent = true
|
||||
nav.navigationBar.setBackgroundImage(UIImage(), for: .default)
|
||||
nav.navigationBar.shadowImage = UIImage()
|
||||
nav.view.backgroundColor = .clear
|
||||
nav.tabBarItem = createTabBarItem(
|
||||
title: tabTitle,
|
||||
normalImage: normalImage,
|
||||
selectedImage: selectedImage
|
||||
)
|
||||
return nav
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UITabBarControllerDelegate
|
||||
|
||||
@@ -23,6 +23,21 @@
|
||||
#import "EPMomentCell.h"
|
||||
#import "EPMineHeaderView.h"
|
||||
|
||||
// MARK: - QCloud SDK
|
||||
#import <QCloudCOSXML/QCloudCOSXML.h>
|
||||
|
||||
// MARK: - Image Upload & Progress HUD
|
||||
#import "MBProgressHUD.h"
|
||||
|
||||
// MARK: - API & Models
|
||||
#import "Api+Moments.h"
|
||||
#import "Api+Mine.h"
|
||||
#import "AccountInfoStorage.h"
|
||||
|
||||
// MARK: - Utilities
|
||||
#import "UIImage+Utils.h"
|
||||
#import "NSString+Utils.h"
|
||||
|
||||
// 注意:
|
||||
// 1. EPMomentViewController 和 EPMineViewController 直接继承 UIViewController
|
||||
// 2. 不继承 BaseViewController(避免 ClientConfig → PIBaseModel 依赖链)
|
||||
|
||||
Reference in New Issue
Block a user