Commit 8613f641 ilCode

刷新加载等基础框架搭建

1 个父辈 4b21ff31
......@@ -17,6 +17,8 @@
5E47C9772C1FD7F6002EA39E /* InfoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E47C9762C1FD7F6002EA39E /* InfoModel.swift */; };
5E47C9792C20157C002EA39E /* InfoDetailController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E47C9782C20157C002EA39E /* InfoDetailController.swift */; };
5E47C97D2C202ED3002EA39E /* AsEmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E47C97C2C202ED3002EA39E /* AsEmptyView.swift */; };
5E65F88B2C2275DB0082D374 /* RefreshFooterAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E65F88A2C2275DB0082D374 /* RefreshFooterAnimator.swift */; };
5E65F88F2C2279160082D374 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5E65F88D2C2279160082D374 /* Localizable.strings */; };
5E66B2D72C1BE2ED00590452 /* UIImage+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E66B2D62C1BE2ED00590452 /* UIImage+Ext.swift */; };
5E66B2D92C1C295100590452 /* UIButton+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E66B2D82C1C295100590452 /* UIButton+Ext.swift */; };
5E6CF00A2C12FAD600BF3CF5 /* TargetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E6CF0092C12FAD600BF3CF5 /* TargetController.swift */; };
......@@ -71,6 +73,8 @@
5EC03E4E2C1155720068A5CB /* CacheTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EC03E4D2C1155720068A5CB /* CacheTools.swift */; };
5EC03E502C118A420068A5CB /* AppInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EC03E4F2C118A420068A5CB /* AppInfo.swift */; };
5EC03E532C118FA00068A5CB /* ScoreProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EC03E522C118FA00068A5CB /* ScoreProvider.swift */; };
5EC6E64A2C211735006F778C /* RefreshHeaderAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EC6E6482C211735006F778C /* RefreshHeaderAnimator.swift */; };
5EC6E64E2C2192C9006F778C /* MatchInfoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EC6E64D2C2192C9006F778C /* MatchInfoCell.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
......@@ -84,6 +88,10 @@
5E47C9762C1FD7F6002EA39E /* InfoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoModel.swift; sourceTree = "<group>"; };
5E47C9782C20157C002EA39E /* InfoDetailController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoDetailController.swift; sourceTree = "<group>"; };
5E47C97C2C202ED3002EA39E /* AsEmptyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsEmptyView.swift; sourceTree = "<group>"; };
5E65F88A2C2275DB0082D374 /* RefreshFooterAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshFooterAnimator.swift; sourceTree = "<group>"; };
5E65F88E2C2279160082D374 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
5E65F8902C227A1C0082D374 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/LaunchScreen.strings"; sourceTree = "<group>"; };
5E65F8912C227A1C0082D374 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; };
5E66B2D62C1BE2ED00590452 /* UIImage+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Ext.swift"; sourceTree = "<group>"; };
5E66B2D82C1C295100590452 /* UIButton+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+Ext.swift"; sourceTree = "<group>"; };
5E6CF0092C12FAD600BF3CF5 /* TargetController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TargetController.swift; sourceTree = "<group>"; };
......@@ -142,6 +150,8 @@
5EC03E4D2C1155720068A5CB /* CacheTools.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheTools.swift; sourceTree = "<group>"; };
5EC03E4F2C118A420068A5CB /* AppInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfo.swift; sourceTree = "<group>"; };
5EC03E522C118FA00068A5CB /* ScoreProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScoreProvider.swift; sourceTree = "<group>"; };
5EC6E6482C211735006F778C /* RefreshHeaderAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefreshHeaderAnimator.swift; sourceTree = "<group>"; };
5EC6E64D2C2192C9006F778C /* MatchInfoCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchInfoCell.swift; sourceTree = "<group>"; };
EA8E48B8C50E8D94161797C6 /* Pods-AoleiSports.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AoleiSports.release.xcconfig"; path = "Target Support Files/Pods-AoleiSports/Pods-AoleiSports.release.xcconfig"; sourceTree = "<group>"; };
F63DEFD57EAA7FD3A430F57E /* Pods_AoleiSports.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AoleiSports.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
......@@ -187,6 +197,7 @@
isa = PBXGroup;
children = (
5E47C9722C1FCB87002EA39E /* InfoController.swift */,
5EC6E64D2C2192C9006F778C /* MatchInfoCell.swift */,
5E47C9782C20157C002EA39E /* InfoDetailController.swift */,
5E47C9742C1FD35E002EA39E /* InfoProvider.swift */,
5E47C9762C1FD7F6002EA39E /* InfoModel.swift */,
......@@ -259,6 +270,7 @@
isa = PBXGroup;
children = (
5E9A19F92C0EAC0300321AC5 /* Info.plist */,
5E65F88D2C2279160082D374 /* Localizable.strings */,
5E6CF0202C1305DD00BF3CF5 /* AoleiSports.xcdatamodeld */,
);
path = Config;
......@@ -267,9 +279,9 @@
5E9A1A0B2C0EAFDC00321AC5 /* Src */ = {
isa = PBXGroup;
children = (
5E9A1A462C0F0D9E00321AC5 /* Base */,
5E9A1A3A2C0F00A000321AC5 /* Utils */,
5E9A1A372C0EFF0C00321AC5 /* Ext */,
5E9A1A3A2C0F00A000321AC5 /* Utils */,
5E9A1A462C0F0D9E00321AC5 /* Base */,
5E9A1A122C0EF4E200321AC5 /* Vendors */,
5E47C97B2C2018AD002EA39E /* Root */,
5E47C97A2C20186D002EA39E /* Score */,
......@@ -282,6 +294,7 @@
5E9A1A122C0EF4E200321AC5 /* Vendors */ = {
isa = PBXGroup;
children = (
5EC6E6492C211735006F778C /* Refresh */,
5E9A1A252C0EF51600321AC5 /* GKNavigationBarSwift */,
);
path = Vendors;
......@@ -353,6 +366,15 @@
path = Base;
sourceTree = "<group>";
};
5EC6E6492C211735006F778C /* Refresh */ = {
isa = PBXGroup;
children = (
5EC6E6482C211735006F778C /* RefreshHeaderAnimator.swift */,
5E65F88A2C2275DB0082D374 /* RefreshFooterAnimator.swift */,
);
path = Refresh;
sourceTree = "<group>";
};
C05EA2A76F80BD312DE7A52C /* Frameworks */ = {
isa = PBXGroup;
children = (
......@@ -401,11 +423,12 @@
};
buildConfigurationList = 5E9A19E32C0EAC0200321AC5 /* Build configuration list for PBXProject "AoleiSports" */;
compatibilityVersion = "Xcode 14.0";
developmentRegion = en;
developmentRegion = "zh-Hans";
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
"zh-Hans",
);
mainGroup = 5E9A19DF2C0EAC0200321AC5;
productRefGroup = 5E9A19E92C0EAC0200321AC5 /* Products */;
......@@ -426,6 +449,7 @@
5E9A1A072C0EAF7600321AC5 /* launch_foot.png in Resources */,
5E9A19F52C0EAC0300321AC5 /* Assets.xcassets in Resources */,
5E9A1A052C0EAF7600321AC5 /* goal.mp3 in Resources */,
5E65F88F2C2279160082D374 /* Localizable.strings in Resources */,
5E9A1A2A2C0EF51600321AC5 /* GKNavigationBarSwift.bundle in Resources */,
5E9A1A082C0EAF7600321AC5 /* red.mp3 in Resources */,
5E9A1A092C0EAF7600321AC5 /* yellow.mp3 in Resources */,
......@@ -510,6 +534,7 @@
5EC03E4A2C1155530068A5CB /* NetworkTools.swift in Sources */,
5E9165DA2C1D9A09004A3C5E /* ServerApi.swift in Sources */,
5E9A1A342C0EF51600321AC5 /* UIScrollView+GKExtension.swift in Sources */,
5EC6E64A2C211735006F778C /* RefreshHeaderAnimator.swift in Sources */,
5E9A1A402C0F04CC00321AC5 /* HomeController.swift in Sources */,
5E47C97D2C202ED3002EA39E /* AsEmptyView.swift in Sources */,
5E47C9792C20157C002EA39E /* InfoDetailController.swift in Sources */,
......@@ -527,6 +552,7 @@
5E9A1A422C0F04DB00321AC5 /* ProfileController.swift in Sources */,
5E9A1A352C0EF51600321AC5 /* UIViewController+GKExtension.swift in Sources */,
5E93B48C2C1A985F00CD6536 /* LoginController.swift in Sources */,
5E65F88B2C2275DB0082D374 /* RefreshFooterAnimator.swift in Sources */,
5EC03E4E2C1155720068A5CB /* CacheTools.swift in Sources */,
5E9A1A2F2C0EF51600321AC5 /* UIBarButtonItem+GKExtension.swift in Sources */,
5EA670622C104D2800CEEA01 /* LoggerTools.swift in Sources */,
......@@ -548,6 +574,7 @@
5E1E66B42C19AAC7009339F0 /* UpdateProfileController.swift in Sources */,
5E93B4882C1A7DA200CD6536 /* BaseTableViewCell.swift in Sources */,
5E9A1A272C0EF51600321AC5 /* GKBaseAnimatedTransition.swift in Sources */,
5EC6E64E2C2192C9006F778C /* MatchInfoCell.swift in Sources */,
5E9A1A322C0EF51600321AC5 /* UINavigationController+GKGesture.swift in Sources */,
5E9A1A2D2C0EF51600321AC5 /* GKPopAnimatedTransition.swift in Sources */,
5EAA223F2C17F87800404739 /* UIView+Ext.swift in Sources */,
......@@ -562,11 +589,21 @@
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
5E65F88D2C2279160082D374 /* Localizable.strings */ = {
isa = PBXVariantGroup;
children = (
5E65F88E2C2279160082D374 /* en */,
5E65F8912C227A1C0082D374 /* zh-Hans */,
);
name = Localizable.strings;
sourceTree = "<group>";
};
5E9A19F62C0EAC0300321AC5 /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
5E9A19F72C0EAC0300321AC5 /* Base */,
5E9A1A0F2C0EB68800321AC5 /* en */,
5E65F8902C227A1C0082D374 /* zh-Hans */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
......@@ -579,6 +616,7 @@
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
......@@ -642,6 +680,7 @@
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
......
/*
Localizable.strings
AoleiSports
Created by ilCode on 2024/6/19.
*/
"Loading more" = "Loading more";
"No more data" = "No more data";
"Loading..." = "Loading...";
/*
Localizable.strings
AoleiSports
Created by ilCode on 2024/6/19.
*/
"Loading more" = "加载更多";
"No more data" = "没有更多数据";
"Loading..." = "加载中...";
{
"images" : [
{
"filename" : "refresh_icon.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "refresh_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "refresh_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
......@@ -11,6 +11,7 @@ import UIKit
enum EmptyType {
case netErr // 网络问题
case noData // 无数据
case other // 其他错误
}
// 缺省页
......@@ -22,21 +23,13 @@ class AsEmptyView: UIView {
return iv
}()
private lazy var netLab: UILabel = {
private lazy var messageLab: UILabel = {
let lab = UILabel()
lab.text = "请检查您的网络设置或刷新重试"
lab.textColor = kSubTitleColor
lab.textAlignment = .center
lab.font = kFontSize(14)
return lab
}()
private lazy var noDataLab: UILabel = {
let lab = UILabel()
lab.text = "暂无数据"
lab.text = ""
lab.textColor = kSubTitleColor
lab.textAlignment = .center
lab.font = kFontSize(14)
lab.numberOfLines = 0
return lab
}()
......@@ -51,26 +44,8 @@ class AsEmptyView: UIView {
return btn
}()
private lazy var netStackView: UIStackView = {
let stackView = UIStackView(arrangedSubviews: [netLab, refreshBtn])
stackView.axis = .vertical
stackView.alignment = .center
stackView.distribution = .equalSpacing
stackView.spacing = 15
return stackView
}()
private lazy var noDataStackView: UIStackView = {
let stackView = UIStackView(arrangedSubviews: [noDataLab])
stackView.axis = .vertical
stackView.alignment = .center
stackView.distribution = .equalSpacing
stackView.spacing = 20
return stackView
}()
private lazy var containerStackView: UIStackView = {
let stackView = UIStackView(arrangedSubviews: [placeHolder, netStackView, noDataStackView])
let stackView = UIStackView(arrangedSubviews: [placeHolder, messageLab, refreshBtn])
stackView.axis = .vertical
stackView.alignment = .center
stackView.distribution = .equalSpacing
......@@ -87,6 +62,8 @@ class AsEmptyView: UIView {
containerStackView.snp.makeConstraints { make in
make.center.equalToSuperview()
make.left.equalToSuperview().offset(10)
make.right.equalToSuperview().offset(-10)
}
refreshBtn.snp.makeConstraints { make in
......@@ -105,14 +82,15 @@ extension AsEmptyView {
if type == .netErr {
placeHolder.image = R.image.net_err()
netStackView.isHidden = false
noDataStackView.isHidden = true
netLab.text = message ?? "请检查您的网络设置或刷新重试"
messageLab.text = message ?? "请检查您的网络设置或刷新重试"
}
else if type == .noData {
placeHolder.image = R.image.no_data()
netStackView.isHidden = true
noDataStackView.isHidden = false
messageLab.text = "暂无数据,请刷新重试"
}
else if type == .other {
placeHolder.image = R.image.no_data()
messageLab.text = message ?? ""
}
}
......@@ -124,3 +102,40 @@ extension AsEmptyView {
refreshAction?()
}
}
////MARK: - UIView+EmptyView
//extension UITableView {
// private enum AssociatedKeys {
// static var emptyViewKey: Void?
// }
//
// private var emptyView: AsEmptyView? {
// get {
// return objc_getAssociatedObject(self, &AssociatedKeys.emptyViewKey) as? AsEmptyView
// }
// set {
// objc_setAssociatedObject(self, &AssociatedKeys.emptyViewKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
// }
// }
//
// // 显示空视图
// func showEmptyView(type: EmptyType, message: String? = nil, refreshAction: (() -> Void)? = nil) {
// if emptyView == nil {
// let ev = AsEmptyView(frame: .zero)
// addSubview(ev)
// ev.snp.makeConstraints { make in
// make.center.equalToSuperview()
// }
// emptyView = ev
// }
// emptyView?.refreshAction = refreshAction
// emptyView?.show(type: type, message: message)
// }
//
// // 隐藏空视图
// func hideEmptyView() {
// emptyView?.dismiss()
// emptyView?.removeFromSuperview()
// emptyView = nil
// }
//}
......@@ -31,6 +31,7 @@ class BaseController: UIViewController {
}
}
//MARK: - Empty处理
extension BaseController {
func showEmpty(type: EmptyType, message: String? = nil) {
emptyView.show(type: type, message: message)
......@@ -41,6 +42,6 @@ extension BaseController {
}
@objc func handleRefresh() {
dismissEmpty()
}
}
......@@ -16,3 +16,14 @@ extension UIImage {
return resizedImage
}
}
extension UIImage {
convenience init(color: UIColor, size: CGSize) {
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
color.setFill()
UIRectFill(CGRect(origin: .zero, size: size))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.init(cgImage: image!.cgImage!)
}
}
......@@ -21,8 +21,8 @@ extension UIView {
}
// 分割线
static func divider(color: UIColor = kDividerColor) -> UIView {
let line = UIView()
static func divider(frame: CGRect = .zero, color: UIColor = kDividerColor) -> UIView {
let line = UIView(frame: frame)
line.backgroundColor = color
return line
}
......
......@@ -9,6 +9,8 @@ import UIKit
// 赛事情报页面
class InfoController: BaseController {
private let MatchInfoCellId = "MatchInfoCell"
var provider: InfoProvider<InfoTarget>?
private lazy var infoListView: UITableView = {
......@@ -17,7 +19,10 @@ class InfoController: BaseController {
tv.dataSource = self
tv.rowHeight = 100
tv.backgroundColor = kMainBgColor
tv.register(MatchInfoCell.self, forCellReuseIdentifier: "MatchInfoCell")
tv.register(MatchInfoCell.self, forCellReuseIdentifier: MatchInfoCellId)
tv.es.addPullToRefresh(animator: RefreshHeaderAnimator(frame: .zero)) { [weak self] in
self?.handleRefresh()
}
return tv
}()
......@@ -25,49 +30,65 @@ class InfoController: BaseController {
super.viewDidLoad()
gk_navTitle = "赛事情报"
provider = InfoProvider(vc: self, showErr: false)
view.addSubview(infoListView)
initProvider()
handleRefresh()
}
deinit {
infoListView.es.removeRefreshHeader()
}
private func initProvider() {
provider = InfoProvider(vc: self, showErr: false)
provider?.infoDataList
.asObservable()
.subscribe(onNext: { infoModels in
print("InfoDataList changed: \(infoModels)")
self.infoListView.reloadData()
if infoModels.isEmpty {
self.infoListView.isHidden = true
self.showEmpty(type: .noData)
} else {
// 数据不为空后面刷新操作换成下拉刷新
self.dismissEmpty()
self.provider?.initLoading = false
self.infoListView.isHidden = false
}
})
.disposed(by: disposeBag)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
handleRefresh()
}
override func handleRefresh() {
super.handleRefresh()
dismissEmpty()
provider?.liveScoreRequest(completion: { logicResult in
if self.provider!.initLoading == false {
self.infoListView.es.stopPullToRefresh()
}
if case .failure(let err) = logicResult {
if case AsError.netErr(let message) = err {
if self.provider!.infoDataList.value.isEmpty && self.provider!.initLoading == true {
self.infoListView.isHidden = true
self.showEmpty(type: .netErr, message: message)
if case AsError.netErr(let message) = err {
self.showEmpty(type: .netErr, message: message)
} else {
self.showEmpty(type: .other, message: err.localizedDescription)
}
} else {
self.view.makeToast(err.localizedDescription, position: .center)
}
return
}
})
}
}
//MARK: UITableViewDelegate, UITableViewDataSource
extension InfoController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return provider?.infoDataList.value.count ?? 0
......@@ -75,51 +96,15 @@ extension InfoController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let model = provider?.infoDataList.value[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "MatchInfoCell", for: indexPath) as! MatchInfoCell
model?.isLast = indexPath.row == (provider?.infoDataList.value.count ?? 0) - 1
let cell = tableView.dequeueReusableCell(withIdentifier: MatchInfoCellId, for: indexPath) as! MatchInfoCell
cell.model = model
return cell
}
}
class MatchInfoCell: BaseTableViewCell {
private lazy var cornerBgView: UIView = {
let view = UIView()
view.backgroundColor = kWhite
view.corners(radius: 10)
return view
}()
private lazy var teamLab: UILabel = {
let lab = UILabel()
lab.textColor = kMainTitleColor
lab.font = kFontSize(16)
return lab
}()
var model: InfoModel? {
didSet {
teamLab.text = (model?.hostName ?? "") + "VS" + (model?.guestName ?? "")
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(cornerBgView)
cornerBgView.addSubview(teamLab)
cornerBgView.snp.makeConstraints { make in
make.left.top.equalToSuperview().offset(5)
make.right.equalToSuperview().offset(-5)
make.bottom.equalToSuperview()
}
teamLab.snp.makeConstraints { make in
make.left.top.equalToSuperview().offset(5)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let infoDetail = InfoDetailController()
infoDetail.model = provider?.infoDataList.value[indexPath.row]
navigationController?.pushViewController(infoDetail, animated: true)
}
}
......@@ -9,15 +9,11 @@ import UIKit
// 情报详情页面
class InfoDetailController: BaseController {
var model: InfoModel? {
didSet {
gk_navTitle = (model?.hostName ?? "") + "VS" + (model?.guestName ?? "")
}
}
var model: InfoModel?
override func viewDidLoad() {
super.viewDidLoad()
gk_navTitle = (model?.hostName ?? "") + "VS" + (model?.guestName ?? "")
}
}
......@@ -27,6 +27,8 @@ class InfoModel: Mappable {
var sportsInfoCount: Int?
var jcInfo: String?
var isLast: Bool?
required init?(map: ObjectMapper.Map) {
}
......
......@@ -58,18 +58,18 @@ enum InfoTarget: TargetType {
class InfoProvider<Target: TargetType>: BaseMoyaProvider<Target> {
var infoDataList = BehaviorRelay<[InfoModel]>(value: [])
func liveScoreRequest(completion: @escaping (Result<Bool, AsError>) -> Void) {
moyaPost(api: APIs.kLiveScore, target: InfoTarget.info) { midResult in
DDLogInfo("赛事情报接口数据:\(midResult)")
// DDLogInfo("赛事情报接口数据:\(midResult)")
switch midResult {
case let .success(midSuccessResult):
guard let matchs = midSuccessResult["matchs"] as? [[String : Any]] else {
guard let matchs = midSuccessResult["matchs"] as? [[String : Any]], !matchs.isEmpty else {
self.infoDataList.accept([])
return
}
// 过滤出 JcInfo 非空的项
// 过滤出JcInfo非空的项
let filteredMatchs = matchs.filter { match in
guard let jcInfo = match["JcInfo"] as? String, !jcInfo.isEmpty else {
return false
......@@ -78,7 +78,6 @@ class InfoProvider<Target: TargetType>: BaseMoyaProvider<Target> {
}
let infoModels = Mapper<InfoModel>().mapArray(JSONArray: filteredMatchs)
self.infoDataList.accept(infoModels)
completion(.success(true))
case .failure(let err):
completion(.failure(err))
......
//
// MatchInfoCell.swift
// AoleiSports
//
// Created by ilCode on 2024/6/18.
//
import UIKit
class MatchInfoCell: BaseTableViewCell {
private lazy var cornerBgView: UIView = {
let view = UIView()
view.backgroundColor = kWhite
view.corners(radius: 5)
return view
}()
private lazy var teamLab: UILabel = {
let lab = UILabel()
lab.textColor = kMainTitleColor
lab.font = kFontSize(16)
return lab
}()
var model: InfoModel? {
didSet {
teamLab.text = (model?.hostName ?? "") + "VS" + (model?.guestName ?? "")
cornerBgView.snp.remakeConstraints { make in
make.left.top.equalToSuperview().offset(8)
make.right.equalToSuperview().offset(-8)
make.bottom.equalToSuperview().offset((model?.isLast ?? false) ? -8 : 0)
}
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.backgroundColor = kMainBgColor
contentView.addSubview(cornerBgView)
cornerBgView.addSubview(teamLab)
cornerBgView.snp.makeConstraints { make in
make.left.top.equalToSuperview().offset(8)
make.right.equalToSuperview().offset(-8)
make.bottom.equalToSuperview()
}
teamLab.snp.makeConstraints { make in
make.left.top.equalToSuperview().offset(5)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
......@@ -23,6 +23,9 @@ class RootController: UITabBarController {
// 设置tabbar选中及未选中的文字颜色
tabBar.tintColor = kMasterColor
tabBar.unselectedItemTintColor = kSubTitleColor
// 自定义分割线
tabBar.addSubview(UIView.divider(frame: CGRect(x: 0, y: 0, width: kScreenW, height: 1)))
addController(rootVC: HomeController(), title: "赛事比分", image: R.image.home(), selectedImage: R.image.home_selected())
addController(rootVC: InfoController(), title: "赛事情报", image: R.image.ssqb(), selectedImage: R.image.ssqb_selected())
......
......@@ -15,3 +15,4 @@
@_exported import SwiftyJSON
@_exported import RxGesture
@_exported import ObjectMapper
@_exported import ESPullToRefresh
......@@ -73,13 +73,15 @@ extension TargetType {
final class BaseMoyaPlugin: PluginType {
var vc: UIViewController?
private var spinner: NVActivityIndicatorView!
var initLoading: Bool = true
var showErr: Bool = true
init(vc: UIViewController?, showLoading: Bool = true, showErr: Bool = true) {
init(vc: UIViewController?, initLoading: Bool = true, showErr: Bool = true) {
self.vc = vc
self.initLoading = initLoading
self.showErr = showErr
if let tmpVC = self.vc, showLoading {
if let tmpVC = self.vc, initLoading {
spinner = NVActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 60, height: 55), type: .ballSpinFadeLoader, color: kMasterColor)
spinner.center = tmpVC.view.center
}
......@@ -96,7 +98,7 @@ final class BaseMoyaPlugin: PluginType {
}
func willSend(_ request: any RequestType, target: any TargetType) {
if let tmpVC = self.vc, let spinner = spinner {
if let tmpVC = self.vc, initLoading, let spinner = spinner {
DispatchQueue.main.async {
tmpVC.view.addSubview(spinner)
spinner.startAnimating()
......@@ -106,7 +108,7 @@ final class BaseMoyaPlugin: PluginType {
func didReceive(_ result: Result<Response, MoyaError>, target: any TargetType) {
// 移除界面中央的活动状态指示器
if let spinner = spinner {
if let spinner = spinner, initLoading {
DispatchQueue.main.async {
spinner.removeFromSuperview()
spinner.stopAnimating()
......@@ -132,10 +134,16 @@ final class BaseMoyaPlugin: PluginType {
class BaseMoyaProvider<Target: TargetType>: MoyaProvider<Target> {
let disposeBag = DisposeBag()
var basePlugin: BaseMoyaPlugin
var initLoading: Bool = true {
didSet {
basePlugin.initLoading = initLoading
}
}
init(vc: UIViewController? = nil, showLoading: Bool = true, showErr: Bool = true) {
self.basePlugin = BaseMoyaPlugin(vc: vc, showLoading: showLoading, showErr: showErr)
init(vc: UIViewController? = nil, initLoading: Bool = true, showErr: Bool = true) {
self.basePlugin = BaseMoyaPlugin(vc: vc, initLoading: initLoading, showErr: showErr)
super.init(plugins: [self.basePlugin])
self.initLoading = initLoading
}
/// Moya Post
......
//
// RefreshFooterAnimator.swift
// AoleiSports
//
// Created by ilCode on 2024/6/19.
//
import UIKit
//MARK: - 自定义刷新尾
public class RefreshFooterAnimator: UIView, ESRefreshProtocol, ESRefreshAnimatorProtocol {
public let loadingMoreDescription: String = "Loading more"
public let noMoreDataDescription: String = "No more data"
public let loadingDescription: String = "Loading..."
public var view: UIView {
return self
}
public var insets: UIEdgeInsets = UIEdgeInsets.zero
public var trigger: CGFloat = 48.0
public var executeIncremental: CGFloat = 48.0
public var state: ESRefreshViewState = .pullToRefresh
private let topLine: UIView = {
let topLine = UIView.init(frame: CGRect.zero)
topLine.backgroundColor = UIColor.init(red: 214/255.0, green: 211/255.0, blue: 206/255.0, alpha: 1.0)
return topLine
}()
private let bottomLine: UIView = {
let bottomLine = UIView.init(frame: CGRect.zero)
bottomLine.backgroundColor = UIColor.init(red: 214/255.0, green: 211/255.0, blue: 206/255.0, alpha: 1.0)
return bottomLine
}()
private let titleLabel: UILabel = {
let label = UILabel.init(frame: CGRect.zero)
label.font = UIFont.systemFont(ofSize: 14.0)
label.textColor = UIColor.init(white: 160.0 / 255.0, alpha: 1.0)
label.textAlignment = .center
return label
}()
private let indicatorView: UIActivityIndicatorView = {
let indicatorView = UIActivityIndicatorView.init(style: .medium)
indicatorView.isHidden = true
return indicatorView
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.white
titleLabel.text = loadingMoreDescription
addSubview(titleLabel)
addSubview(indicatorView)
addSubview(topLine)
addSubview(bottomLine)
}
public required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public func refreshAnimationBegin(view: ESRefreshComponent) {
indicatorView.startAnimating()
indicatorView.isHidden = false
}
public func refreshAnimationEnd(view: ESRefreshComponent) {
indicatorView.stopAnimating()
indicatorView.isHidden = true
}
public func refresh(view: ESRefreshComponent, progressDidChange progress: CGFloat) {
// do nothing
}
public func refresh(view: ESRefreshComponent, stateDidChange state: ESRefreshViewState) {
switch state {
case .refreshing :
titleLabel.text = loadingDescription
break
case .autoRefreshing :
titleLabel.text = loadingDescription
break
case .noMoreData:
titleLabel.text = noMoreDataDescription
break
default:
titleLabel.text = loadingMoreDescription
break
}
}
public override func layoutSubviews() {
super.layoutSubviews()
let s = self.bounds.size
let w = s.width
let h = s.height
titleLabel.frame = self.bounds
indicatorView.center = CGPoint.init(x: 32.0, y: h / 2.0)
topLine.frame = CGRect.init(x: 0.0, y: 0.0, width: w, height: 0.5)
bottomLine.frame = CGRect.init(x: 0.0, y: h - 1.0, width: w, height: 1.0)
}
}
//
// RefreshHeaderAnimator.swift
// LittleDreamSleep
//
// Created by peter on 2022/8/24.
//
import UIKit
//MARK: - 自定义刷新头
class RefreshHeaderAnimator: UIView, ESRefreshAnimatorProtocol {
public var view: UIView { return self }
public var insets: UIEdgeInsets = UIEdgeInsets.zero
public var trigger: CGFloat = 60.0
public var executeIncremental: CGFloat = 60.0
public var state: ESRefreshViewState = .pullToRefresh
let aniImageView = UIImageView(image: R.image.refresh_icon())
override init(frame: CGRect) {
super.init(frame: frame)
aniImageView.frame = CGRect(x: (kScreenW - 30)/2.0, y: (60 - 30)/2.0, width: 30, height: 30)
addSubview(aniImageView)
}
public required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
//MARK: - ESRefreshProtocol
extension RefreshHeaderAnimator: ESRefreshProtocol {
public func refreshAnimationBegin(view: ESRefreshComponent) {
aniImageView.frame = CGRect(x: (self.bounds.size.width - 30)/2.0, y: (self.bounds.size.height - 30)/2.0, width: 30, height: 30)
startAnimation()
}
public func refreshAnimationEnd(view: ESRefreshComponent) {
stopAnimation()
}
public func refresh(view: ESRefreshComponent, progressDidChange progress: CGFloat) { }
public func refresh(view: ESRefreshComponent, stateDidChange state: ESRefreshViewState) {
guard self.state != state else {
return
}
self.state = state
switch state {
case .pullToRefresh:
break
case .releaseToRefresh:
break
default:
break
}
}
}
extension RefreshHeaderAnimator {
@objc func startAnimation() {
let animation = CABasicAnimation(keyPath: "transform.rotation.z")
animation.toValue = NSNumber(value: Double.pi * 2)
animation.duration = 2.0
animation.repeatCount = Float.infinity
aniImageView.layer.add(animation, forKey: "rotationAnimation")
}
@objc func stopAnimation() {
aniImageView.layer.removeAnimation(forKey: "rotationAnimation")
}
}
//MARK: - 3D动画
extension RefreshHeaderAnimator {
// import SceneKit
// var sceneView: SCNView!
// 创建旋转动画
// let animation = CABasicAnimation(keyPath: "transform.rotation.z")
// animation.toValue = NSNumber(value: Double.pi * 2)
// animation.duration = 3.0
// animation.repeatCount = Float.infinity
// aniImageView.layer.add(animation, forKey: nil)
// 创建 SceneKit 视图
// sceneView = SCNView(frame: CGRect(x: (kScreenW - w)/2.0, y: 0, width: 60, height: 60))
// addSubview(sceneView)
// sceneView.border()
// 创建场景
// let scene = SCNScene()
// sceneView.scene = scene
// sceneView.allowsCameraControl = true
// sceneView.autoenablesDefaultLighting = true
// 创建球体几何体
// let sphere = SCNSphere(radius: 1.5)
//
// // 添加球体材质
// let material = SCNMaterial()
// material.diffuse.contents = UIImage(named: "football_texture.jpg")
// sphere.firstMaterial = material
//
// // 创建球体节点
// let sphereNode = SCNNode(geometry: sphere)
// sphereNode.position = SCNVector3(0, 0, 0)
// scene.rootNode.addChildNode(sphereNode)
//
// // 创建绕球体旋转的动画
// let rotateAction = SCNAction.rotateBy(x: 0, y: CGFloat(2 * Double.pi), z: 0, duration: 3)
// let repeatAction = SCNAction.repeatForever(rotateAction)
// sphereNode.runAction(repeatAction)
//
// // 添加相机
// let cameraNode = SCNNode()
// cameraNode.camera = SCNCamera()
// cameraNode.position = SCNVector3(0, 0, 5)
// scene.rootNode.addChildNode(cameraNode)
}
......@@ -15,6 +15,7 @@ target 'AoleiSports' do
pod 'Toast-Swift', '~> 5.1.1'
pod 'ObjectMapper', '~> 4.4.2'
pod 'RxGesture', '~> 4.0.4'
pod 'ESPullToRefresh', '~> 2.9.3'
end
......
......@@ -3,6 +3,7 @@ PODS:
- CocoaLumberjack/Core (3.8.5)
- CocoaLumberjack/Swift (3.8.5):
- CocoaLumberjack/Core
- ESPullToRefresh (2.9.3)
- Moya/Core (15.0.0):
- Alamofire (~> 5.0)
- Moya/RxSwift (15.0.0):
......@@ -29,6 +30,7 @@ PODS:
DEPENDENCIES:
- Alamofire (~> 5.9.1)
- CocoaLumberjack/Swift
- ESPullToRefresh (~> 2.9.3)
- Moya/RxSwift (~> 15.0)
- NVActivityIndicatorView (~> 5.2.0)
- ObjectMapper (~> 4.4.2)
......@@ -44,6 +46,7 @@ SPEC REPOS:
trunk:
- Alamofire
- CocoaLumberjack
- ESPullToRefresh
- Moya
- NVActivityIndicatorView
- ObjectMapper
......@@ -59,6 +62,7 @@ SPEC REPOS:
SPEC CHECKSUMS:
Alamofire: f36a35757af4587d8e4f4bfa223ad10be2422b8c
CocoaLumberjack: 6a459bc897d6d80bd1b8c78482ec7ad05dffc3f0
ESPullToRefresh: 0d68daa602b343c81753a1b95e09fd0bfcddfd40
Moya: 138f0573e53411fb3dc17016add0b748dfbd78ee
NVActivityIndicatorView: fe52a6a68664c2df8991d7d9e3d86d8d19453c53
ObjectMapper: e6e4d91ff7f2861df7aecc536c92d8363f4c9677
......@@ -71,6 +75,6 @@ SPEC CHECKSUMS:
SwiftyJSON: f5b1bf1cd8dd53cd25887ac0eabcfd92301c6a5a
Toast-Swift: 7a03a532afe3a560d4044bc7c237e2864d295173
PODFILE CHECKSUM: 0ed9736004dd278591ebfa22937383fb27c4e17f
PODFILE CHECKSUM: aaab2dc38f3bdf57b45edf6252d34b03cda60517
COCOAPODS: 1.15.2
......@@ -44,25 +44,55 @@ struct _R {
}
struct project {
let developmentRegion = "en"
let developmentRegion = "zh-Hans"
}
/// This `_R.string` struct is generated, and contains static references to 1 localization tables.
/// This `_R.string` struct is generated, and contains static references to 2 localization tables.
struct string {
let bundle: Foundation.Bundle
let preferredLanguages: [String]?
let locale: Locale?
var launchScreen: launchScreen { .init(source: .init(bundle: bundle, tableName: "LaunchScreen", preferredLanguages: preferredLanguages, locale: locale)) }
var localizable: localizable { .init(source: .init(bundle: bundle, tableName: "Localizable", preferredLanguages: preferredLanguages, locale: locale)) }
func launchScreen(preferredLanguages: [String]) -> launchScreen {
.init(source: .init(bundle: bundle, tableName: "LaunchScreen", preferredLanguages: preferredLanguages, locale: locale))
}
func localizable(preferredLanguages: [String]) -> localizable {
.init(source: .init(bundle: bundle, tableName: "Localizable", preferredLanguages: preferredLanguages, locale: locale))
}
/// This `_R.string.launchScreen` struct is generated, and contains static references to 0 localization keys.
struct launchScreen {
let source: RswiftResources.StringResource.Source
}
/// This `_R.string.localizable` struct is generated, and contains static references to 3 localization keys.
struct localizable {
let source: RswiftResources.StringResource.Source
/// zh-Hans translation: 加载更多
///
/// Key: Loading more
///
/// Locales: en, zh-Hans
var loadingMore: RswiftResources.StringResource { .init(key: "Loading more", tableName: "Localizable", source: source, developmentValue: "加载更多", comment: nil) }
/// zh-Hans translation: 加载中...
///
/// Key: Loading...
///
/// Locales: en, zh-Hans
var loading: RswiftResources.StringResource { .init(key: "Loading...", tableName: "Localizable", source: source, developmentValue: "加载中...", comment: nil) }
/// zh-Hans translation: 没有更多数据
///
/// Key: No more data
///
/// Locales: en, zh-Hans
var noMoreData: RswiftResources.StringResource { .init(key: "No more data", tableName: "Localizable", source: source, developmentValue: "没有更多数据", comment: nil) }
}
}
/// This `_R.color` struct is generated, and contains static references to 1 colors.
......@@ -73,7 +103,7 @@ struct _R {
var accentColor: RswiftResources.ColorResource { .init(name: "AccentColor", path: [], bundle: bundle) }
}
/// This `_R.image` struct is generated, and contains static references to 22 images.
/// This `_R.image` struct is generated, and contains static references to 23 images.
struct image {
let bundle: Foundation.Bundle
......@@ -134,6 +164,9 @@ struct _R {
/// Image `profile_selected`.
var profile_selected: RswiftResources.ImageResource { .init(name: "profile_selected", path: [], bundle: bundle, locale: nil, onDemandResourceTags: nil) }
/// Image `refresh_icon`.
var refresh_icon: RswiftResources.ImageResource { .init(name: "refresh_icon", path: [], bundle: bundle, locale: nil, onDemandResourceTags: nil) }
/// Image `right_arrow_icon`.
var right_arrow_icon: RswiftResources.ImageResource { .init(name: "right_arrow_icon", path: [], bundle: bundle, locale: nil, onDemandResourceTags: nil) }
......
支持 Markdown 格式
你添加了 0 到此讨论。请谨慎行事。
Finish editing this message first!