搜索完成

This commit is contained in:
zjx 2025-07-23 16:50:26 +08:00
parent 9f406e7fe0
commit d3cd10c3d7
61 changed files with 2033 additions and 118 deletions

View File

@ -37,6 +37,14 @@
BF02B8222E2FAB1600172177 /* BRAboutUsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF02B8212E2FAB1600172177 /* BRAboutUsViewController.swift */; };
BF02B8242E2FAEB500172177 /* BRAboutUsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF02B8232E2FAEB500172177 /* BRAboutUsCell.swift */; };
BF02B8262E2FB36A00172177 /* BRAboutUsHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF02B8252E2FB36A00172177 /* BRAboutUsHeaderView.swift */; };
BF02B8282E30821B00172177 /* BRSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF02B8272E30821B00172177 /* BRSearchViewController.swift */; };
BF02B82D2E30855300172177 /* BRSearchTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF02B82C2E30855300172177 /* BRSearchTextView.swift */; };
BF02B82F2E30895700172177 /* BRSearchRecordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF02B82E2E30895700172177 /* BRSearchRecordView.swift */; };
BF02B8312E30897700172177 /* BRSearchHomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF02B8302E30897700172177 /* BRSearchHomeView.swift */; };
BF02B8332E308E4300172177 /* BRSearchRecordTagCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF02B8322E308E4300172177 /* BRSearchRecordTagCell.swift */; };
BF02B8362E30ACEE00172177 /* BRSearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF02B8352E30ACEE00172177 /* BRSearchViewModel.swift */; };
BF02B8392E30B30400172177 /* AlignedCollectionViewFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF02B8382E30B30400172177 /* AlignedCollectionViewFlowLayout.swift */; };
BF02B83B2E30BB4C00172177 /* BRHotSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF02B83A2E30BB4C00172177 /* BRHotSearchView.swift */; };
BF0DBDD12E0D4E150035F6B4 /* BRTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0DBDD02E0D4E150035F6B4 /* BRTabBar.swift */; };
BF3338E82E15219500B10F76 /* UINavigationBar+BRAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3338E72E15218F00B10F76 /* UINavigationBar+BRAdd.swift */; };
BF3338EA2E152B8100B10F76 /* BRPlayerCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3338E92E152B8100B10F76 /* BRPlayerCache.swift */; };
@ -48,6 +56,9 @@
BF3338F92E16178700B10F76 /* BRDetailControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3338F82E16178700B10F76 /* BRDetailControlView.swift */; };
BF3338FB2E161CF900B10F76 /* NSNumber+BRAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3338FA2E161CF000B10F76 /* NSNumber+BRAdd.swift */; };
BF3338FD2E1626B000B10F76 /* BRPlayerControlProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3338FC2E1626A500B10F76 /* BRPlayerControlProtocol.swift */; };
BF3A56812E30C08F009E5CF9 /* BRHotSearchTagCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3A56802E30C08F009E5CF9 /* BRHotSearchTagCell.swift */; };
BF3A56832E30C561009E5CF9 /* BRSearchResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3A56822E30C561009E5CF9 /* BRSearchResultView.swift */; };
BF3A56852E30CA78009E5CF9 /* BRSearchResultCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3A56842E30CA78009E5CF9 /* BRSearchResultCell.swift */; };
BF692AEB2E0A475D00A5C2DA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF692AE12E0A475D00A5C2DA /* AppDelegate.swift */; };
BF692AEC2E0A475D00A5C2DA /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF692AE82E0A475D00A5C2DA /* SceneDelegate.swift */; };
BF692AEE2E0A475D00A5C2DA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF692AE22E0A475D00A5C2DA /* Assets.xcassets */; };
@ -171,6 +182,14 @@
BF02B8212E2FAB1600172177 /* BRAboutUsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRAboutUsViewController.swift; sourceTree = "<group>"; };
BF02B8232E2FAEB500172177 /* BRAboutUsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRAboutUsCell.swift; sourceTree = "<group>"; };
BF02B8252E2FB36A00172177 /* BRAboutUsHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRAboutUsHeaderView.swift; sourceTree = "<group>"; };
BF02B8272E30821B00172177 /* BRSearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRSearchViewController.swift; sourceTree = "<group>"; };
BF02B82C2E30855300172177 /* BRSearchTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRSearchTextView.swift; sourceTree = "<group>"; };
BF02B82E2E30895700172177 /* BRSearchRecordView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRSearchRecordView.swift; sourceTree = "<group>"; };
BF02B8302E30897700172177 /* BRSearchHomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRSearchHomeView.swift; sourceTree = "<group>"; };
BF02B8322E308E4300172177 /* BRSearchRecordTagCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRSearchRecordTagCell.swift; sourceTree = "<group>"; };
BF02B8352E30ACEE00172177 /* BRSearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRSearchViewModel.swift; sourceTree = "<group>"; };
BF02B8382E30B30400172177 /* AlignedCollectionViewFlowLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlignedCollectionViewFlowLayout.swift; sourceTree = "<group>"; };
BF02B83A2E30BB4C00172177 /* BRHotSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRHotSearchView.swift; sourceTree = "<group>"; };
BF0DBDD02E0D4E150035F6B4 /* BRTabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRTabBar.swift; sourceTree = "<group>"; };
BF3338E72E15218F00B10F76 /* UINavigationBar+BRAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationBar+BRAdd.swift"; sourceTree = "<group>"; };
BF3338E92E152B8100B10F76 /* BRPlayerCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRPlayerCache.swift; sourceTree = "<group>"; };
@ -182,6 +201,9 @@
BF3338F82E16178700B10F76 /* BRDetailControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRDetailControlView.swift; sourceTree = "<group>"; };
BF3338FA2E161CF000B10F76 /* NSNumber+BRAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSNumber+BRAdd.swift"; sourceTree = "<group>"; };
BF3338FC2E1626A500B10F76 /* BRPlayerControlProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRPlayerControlProtocol.swift; sourceTree = "<group>"; };
BF3A56802E30C08F009E5CF9 /* BRHotSearchTagCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRHotSearchTagCell.swift; sourceTree = "<group>"; };
BF3A56822E30C561009E5CF9 /* BRSearchResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRSearchResultView.swift; sourceTree = "<group>"; };
BF3A56842E30CA78009E5CF9 /* BRSearchResultCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRSearchResultCell.swift; sourceTree = "<group>"; };
BF692AC92E0A475500A5C2DA /* BeeReel.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BeeReel.app; sourceTree = BUILT_PRODUCTS_DIR; };
BF692AE12E0A475D00A5C2DA /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
BF692AE22E0A475D00A5C2DA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@ -394,6 +416,55 @@
path = WebView;
sourceTree = "<group>";
};
BF02B8292E30851900172177 /* Search */ = {
isa = PBXGroup;
children = (
BF02B82A2E30852700172177 /* Controller */,
BF02B82B2E30852F00172177 /* View */,
BF02B8342E30ACCE00172177 /* ViewModel */,
);
path = Search;
sourceTree = "<group>";
};
BF02B82A2E30852700172177 /* Controller */ = {
isa = PBXGroup;
children = (
BF02B8272E30821B00172177 /* BRSearchViewController.swift */,
);
path = Controller;
sourceTree = "<group>";
};
BF02B82B2E30852F00172177 /* View */ = {
isa = PBXGroup;
children = (
BF3A56822E30C561009E5CF9 /* BRSearchResultView.swift */,
BF3A56842E30CA78009E5CF9 /* BRSearchResultCell.swift */,
BF02B8302E30897700172177 /* BRSearchHomeView.swift */,
BF02B82C2E30855300172177 /* BRSearchTextView.swift */,
BF02B82E2E30895700172177 /* BRSearchRecordView.swift */,
BF02B8322E308E4300172177 /* BRSearchRecordTagCell.swift */,
BF02B83A2E30BB4C00172177 /* BRHotSearchView.swift */,
BF3A56802E30C08F009E5CF9 /* BRHotSearchTagCell.swift */,
);
path = View;
sourceTree = "<group>";
};
BF02B8342E30ACCE00172177 /* ViewModel */ = {
isa = PBXGroup;
children = (
BF02B8352E30ACEE00172177 /* BRSearchViewModel.swift */,
);
path = ViewModel;
sourceTree = "<group>";
};
BF02B8372E30B2F800172177 /* AlignedCollectionViewFlowLayout */ = {
isa = PBXGroup;
children = (
BF02B8382E30B30400172177 /* AlignedCollectionViewFlowLayout.swift */,
);
path = AlignedCollectionViewFlowLayout;
sourceTree = "<group>";
};
BF3338ED2E15566C00B10F76 /* Explore */ = {
isa = PBXGroup;
children = (
@ -490,6 +561,7 @@
BF692AF52E0A47D400A5C2DA /* Class */ = {
isa = PBXGroup;
children = (
BF02B8292E30851900172177 /* Search */,
BF02B8102E2F83A100172177 /* Mine */,
BF02B8052E2F612200172177 /* Favorites */,
BF3338ED2E15566C00B10F76 /* Explore */,
@ -515,6 +587,7 @@
BF692AF72E0A480E00A5C2DA /* Thirdparty */ = {
isa = PBXGroup;
children = (
BF02B8372E30B2F800172177 /* AlignedCollectionViewFlowLayout */,
BFC676A62E12AF04006659E5 /* FlowLayout */,
BFC676612E0E2C8E006659E5 /* WMZBanner */,
BF692B292E0A84F700A5C2DA /* JXUUID */,
@ -1016,6 +1089,7 @@
BFC676732E0E938B006659E5 /* BRTableView.swift in Sources */,
BFC676932E126A62006659E5 /* BRSpotlightNewMainCell.swift in Sources */,
BFC6768D2E123D6E006659E5 /* AttributedString+BRAdd.swift in Sources */,
BF02B8392E30B30400172177 /* AlignedCollectionViewFlowLayout.swift in Sources */,
BF3338F52E1616B200B10F76 /* BRExploreControlView.swift in Sources */,
BF692B132E0A7B9000A5C2DA /* BRUserInfo.swift in Sources */,
BF692B042E0A76D200A5C2DA /* BRLoginManager.swift in Sources */,
@ -1031,6 +1105,7 @@
BF02B7E12E2DE64200172177 /* BRVideoProgressView.swift in Sources */,
BF692B542E0AA8FA00A5C2DA /* BRCollectionView.swift in Sources */,
BF3338E82E15219500B10F76 /* UINavigationBar+BRAdd.swift in Sources */,
BF02B82D2E30855300172177 /* BRSearchTextView.swift in Sources */,
BF692B472E0A9B7900A5C2DA /* BRPlayer.swift in Sources */,
BF692B6E2E0BD4CB00A5C2DA /* BRHomeHeaderView.swift in Sources */,
BF692AFA2E0A6F0900A5C2DA /* BRNetwork.swift in Sources */,
@ -1039,14 +1114,17 @@
BF692B7C2E0D3C1300A5C2DA /* BRVideoInfoModel.swift in Sources */,
BFC6766D2E0E3A8D006659E5 /* BRImageView.swift in Sources */,
BF02B7ED2E2E390500172177 /* BRScrollView.swift in Sources */,
BF3A56812E30C08F009E5CF9 /* BRHotSearchTagCell.swift in Sources */,
BF02B8132E2F83C200172177 /* BRMineViewController.swift in Sources */,
BF02B8222E2FAB1600172177 /* BRAboutUsViewController.swift in Sources */,
BFC6766F2E0E3B5C006659E5 /* UIImageView+BRAdd.swift in Sources */,
BF692B782E0D3A1200A5C2DA /* BRHomeModuleItem.swift in Sources */,
BF692B5A2E0AAADD00A5C2DA /* BRPlayerListCell.swift in Sources */,
BF02B8312E30897700172177 /* BRSearchHomeView.swift in Sources */,
BF692B162E0A7CD600A5C2DA /* BRHUD.swift in Sources */,
BF3338F72E16176900B10F76 /* BRDetailPlayerCell.swift in Sources */,
BF3338EA2E152B8100B10F76 /* BRPlayerCache.swift in Sources */,
BF3A56832E30C561009E5CF9 /* BRSearchResultView.swift in Sources */,
BFC676952E126BBF006659E5 /* BRSpotlightNewCell.swift in Sources */,
BF692B402E0A8FA100A5C2DA /* UIColor+BRAdd.swift in Sources */,
BF692B102E0A7B4300A5C2DA /* BRUserDefaultsKey.swift in Sources */,
@ -1054,6 +1132,7 @@
BFC676912E126248006659E5 /* BRSpotlightTopCell.swift in Sources */,
BFC676B72E137DFC006659E5 /* BRPopularPicksCell.swift in Sources */,
BF692B422E0A8FB500A5C2DA /* UIFont+BRAdd.swift in Sources */,
BF02B8362E30ACEE00172177 /* BRSearchViewModel.swift in Sources */,
BF692AEC2E0A475D00A5C2DA /* SceneDelegate.swift in Sources */,
BF692B492E0A9D0E00A5C2DA /* UIView+BRAdd.swift in Sources */,
BF02B7F82E2F211A00172177 /* BRHomeCategoriesMainCell.swift in Sources */,
@ -1078,6 +1157,8 @@
BF692B582E0AAA6F00A5C2DA /* UIScreen+BRAdd.swift in Sources */,
BF692B1F2E0A804600A5C2DA /* BRLocalizedManager.swift in Sources */,
BF02B7E92E2E29E900172177 /* BREpisodeSelectorCell.swift in Sources */,
BF02B83B2E30BB4C00172177 /* BRHotSearchView.swift in Sources */,
BF02B8332E308E4300172177 /* BRSearchRecordTagCell.swift in Sources */,
BF692B612E0B814F00A5C2DA /* BRTabBarItemContentView.swift in Sources */,
BF02B7F12E2E55E300172177 /* BRRateSelectorView.swift in Sources */,
BF692B012E0A74A200A5C2DA /* BRDefine.swift in Sources */,
@ -1120,14 +1201,17 @@
BF692B562E0AA92100A5C2DA /* BRCollectionViewCell.swift in Sources */,
BF02B7E32E2E08BD00172177 /* BRDetailEpButton.swift in Sources */,
BF692B072E0A771C00A5C2DA /* BRModel.swift in Sources */,
BF02B82F2E30895700172177 /* BRSearchRecordView.swift in Sources */,
BF692B752E0D39D000A5C2DA /* BRListModel.swift in Sources */,
BF02B8172E2F881200172177 /* BRMineItem.swift in Sources */,
BFC676B92E1385FC006659E5 /* BRPopularPicksSmallCell.swift in Sources */,
BF692B512E0AA8C600A5C2DA /* BRPlayerListViewController.swift in Sources */,
BF02B8282E30821B00172177 /* BRSearchViewController.swift in Sources */,
BFC676522E0D4EFD006659E5 /* BRHomeViewModel.swift in Sources */,
BFC676A72E12AF04006659E5 /* WaterfallMutiSectionFlowLayout.swift in Sources */,
BF692B2A2E0A84F700A5C2DA /* JXUUID.m in Sources */,
BF692B2B2E0A84F700A5C2DA /* PDKeyChain.m in Sources */,
BF3A56852E30CA78009E5CF9 /* BRSearchResultCell.swift in Sources */,
BF02B7FA2E2F225D00172177 /* BRHomeCategoryModel.swift in Sources */,
BFC6766B2E0E395F006659E5 /* BRHomeHeaderBannerCell.swift in Sources */,
);

View File

@ -6,13 +6,14 @@
//
import UIKit
import FDFullscreenPopGesture
class BRNavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
fd_fullscreenPopGestureRecognizer.isEnabled = true
// Do any additional setup after loading the view.
}
override func pushViewController(_ viewController: UIViewController, animated: Bool) {

View File

@ -10,3 +10,24 @@ import SmartCodable
extension String: SmartCodable {
}
extension String {
func br_range(of searchString: String) -> [NSRange] {
do {
let newSearch = searchString.lowercased()
let newText = self.lowercased()
let regex = try NSRegularExpression(pattern: newSearch)
let matches = regex.matches(in: newText, range: NSRange(self.startIndex..., in: newText))
let ranges = matches.map { $0.range }
return ranges
} catch {
return []
}
}
}

View File

@ -94,4 +94,32 @@ extension UIColor {
static func colorB7B7B7(alpha: CGFloat = 1) -> UIColor {
return UIColor(rgb: 0xB7B7B7, alpha: alpha)
}
static func colorF2F2F2(alpha: CGFloat = 1) -> UIColor {
return UIColor(rgb: 0xF2F2F2, alpha: alpha)
}
static func colorFFF3F5(alpha: CGFloat = 1) -> UIColor {
return UIColor(rgb: 0xFFF3F5, alpha: alpha)
}
static func colorE4FEFF(alpha: CGFloat = 1) -> UIColor {
return UIColor(rgb: 0xE4FEFF, alpha: alpha)
}
static func colorFFFDF1(alpha: CGFloat = 1) -> UIColor {
return UIColor(rgb: 0xFFFDF1, alpha: alpha)
}
static func colorFFD8DE(alpha: CGFloat = 1) -> UIColor {
return UIColor(rgb: 0xFFD8DE, alpha: alpha)
}
static func color82F8FF(alpha: CGFloat = 1) -> UIColor {
return UIColor(rgb: 0x82F8FF, alpha: alpha)
}
static func colorFFEFA1(alpha: CGFloat = 1) -> UIColor {
return UIColor(rgb: 0xFFEFA1, alpha: alpha)
}
}

View File

@ -81,4 +81,30 @@ class BRHomeAPI {
completer?(response.data)
}
}
///
static func requestHotSearchList(completer: ((_ list: [BRShortModel]?) -> Void)?) {
var param = BRNetworkParameters(path: "/search/hots")
param.isLoding = false
param.isToast = false
param.method = .get
BRNetwork.request(parameters: param) { (response: BRNetworkResponse<BRListModel<BRShortModel>>) in
completer?(response.data?.list)
}
}
///
static func requestSearch(text: String, completer: ((_ list: [BRShortModel]?) -> Void)?) {
var param = BRNetworkParameters(path: "/search")
param.method = .get
param.parameters = [
"search" : text
]
BRNetwork.request(parameters: param) { (response: BRNetworkResponse<BRListModel<BRShortModel>>) in
completer?(response.data?.list)
}
}
}

View File

@ -77,7 +77,8 @@ extension BRNetworkTarget: TargetType {
"system-type" : "ios",
"idfa" : JXUUID.idfa(),
"model" : UIDevice.br_machineModelName(),
"authorization" : userToken
"authorization" : userToken,
"device-gaid" : JXUUID.idfv()
]
return dic
}

View File

@ -32,6 +32,7 @@ class BRExploreViewController: BRPlayerListViewController {
private lazy var searchButton: UIButton = {
let button = UIButton(type: .custom)
button.setImage(UIImage(named: "search_icon_01"), for: .normal)
button.addTarget(self, action: #selector(handleSearchButton), for: .touchUpInside)
return button
}()
@ -158,7 +159,6 @@ extension BRExploreViewController {
BRVideoAPI.requestFavorite(isFavorite: isFavorite, shortPlayId: shortPlayId, videoId: shortModel?.short_play_video_id) {
}
}
@objc private func handleEpisodeButton() {
@ -167,6 +167,11 @@ extension BRExploreViewController {
self.navigationController?.pushViewController(vc, animated: true)
}
@objc private func handleSearchButton() {
let vc = BRSearchViewController()
self.navigationController?.pushViewController(vc, animated: true)
}
}

View File

@ -15,6 +15,12 @@ class BRFavoritesViewController: BRViewController {
///
private var playHistoryModel: BRShortModel? {
didSet {
if let _ = playHistoryModel {
self.collectionViewLayout.headerReferenceSize = .init(width: UIScreen.width, height: 180)
} else {
self.collectionViewLayout.headerReferenceSize = .zero
}
collectionView.reloadData()
}
}
@ -33,7 +39,7 @@ class BRFavoritesViewController: BRViewController {
layout.minimumInteritemSpacing = 11
layout.sectionInset = .init(top: 0, left: 15, bottom: 0, right: 15)
layout.itemSize = .init(width: width, height: height)
layout.headerReferenceSize = .init(width: UIScreen.width, height: 180)
layout.headerReferenceSize = .zero
return layout
}()
@ -41,6 +47,12 @@ class BRFavoritesViewController: BRViewController {
let collectionView = BRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.br_addRefreshHeader { [weak self] in
self?.handleHeaderRefresh(nil)
}
collectionView.br_addRefreshBackFooter { [weak self] in
self?.handleFooterRefresh(nil)
}
collectionView.register(BRFavoritesCell.self, forCellWithReuseIdentifier: "cell")
collectionView.register(BRFavoritesHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "headerView")
return collectionView
@ -71,7 +83,26 @@ class BRFavoritesViewController: BRViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(true, animated: true)
}
override func viewDidAppear(_ animated: Bool) {
if self.hasViewDidAppear {
requestPlayHistorys()
}
super.viewDidAppear(animated)
}
override func handleHeaderRefresh(_ completer: (() -> Void)?) {
self.requestPlayHistorys()
self.requestDataList(page: 1) { [weak self] in
self?.collectionView.br_endHeaderRefreshing()
}
}
override func handleFooterRefresh(_ completer: (() -> Void)?) {
self.requestDataList(page: self.page + 1) { [weak self] in
self?.collectionView.br_endFooterRefreshing()
}
}

View File

@ -133,6 +133,7 @@ class BRHomeViewController: BRViewController {
private lazy var searchButton: UIButton = {
let button = UIButton(type: .custom)
button.setImage(UIImage(named: "search_button_01"), for: .normal)
button.addTarget(self, action: #selector(handleSearchButton), for: .touchUpInside)
return button
}()
@ -237,6 +238,14 @@ extension BRHomeViewController {
}
extension BRHomeViewController {
@objc private func handleSearchButton() {
let vc = BRSearchViewController()
self.navigationController?.pushViewController(vc, animated: true)
}
}
extension BRHomeViewController {
private func requestHomeData(completer: (() -> Void)? = nil) {

View File

@ -15,22 +15,6 @@ class BRShortModel: BRModel, SmartCodable {
case r_720 = "720"
case r_1080 = "1080"
var needLogin: Bool {
if self == .r_720 {
return true
} else {
return false
}
}
var needVip: Bool {
if self == .r_1080 {
return true
} else {
return false
}
}
var toString: String {
return "\(self.rawValue)P"
}
@ -67,11 +51,6 @@ class BRShortModel: BRModel, SmartCodable {
var revolution: VideoRevolution?
@IgnoredKey
var titleAttributedString: NSAttributedString?
@IgnoredKey
var br_isSelected: Bool?
static func mappingForKey() -> [SmartKeyTransformer]? {
return [

View File

@ -33,7 +33,7 @@ class BRAboutUsViewController: BRViewController {
}()
private lazy var headerView: BRAboutUsHeaderView = {
let view = BRAboutUsHeaderView(frame: .init(x: 0, y: 0, width: UIScreen.width, height: 200))
let view = BRAboutUsHeaderView(frame: .init(x: 0, y: 0, width: UIScreen.width, height: 250))
return view
}()

View File

@ -17,7 +17,7 @@ class BRMineViewController: BRViewController {
],
[
BRMineItem(type: .web, icon: UIImage(named: "mine_item_icon_01"), title: "Privacy Policy".localized, url: kSBPrivacyPolicyWebUrl),
BRMineItem(type: .helpCenter, icon: UIImage(named: "mine_item_icon_02"), title: "Help Center".localized, url: ""),
// BRMineItem(type: .helpCenter, icon: UIImage(named: "mine_item_icon_02"), title: "Help Center".localized, url: ""),
BRMineItem(type: .web, icon: UIImage(named: "mine_item_icon_03"), title: "User Agreement".localized, url: kSBUserAgreementWebUrl),
BRMineItem(type: .aboutUs, icon: UIImage(named: "mine_item_icon_04"), title: "About Us".localized, url: ""),
]
@ -41,9 +41,14 @@ class BRMineViewController: BRViewController {
return tableView
}()
deinit {
NotificationCenter.default.removeObserver(self)
}
override func viewDidLoad() {
super.viewDidLoad()
self.edgesForExtendedLayout = [.top, .bottom]
NotificationCenter.default.addObserver(self, selector: #selector(userInfoUpdateNotification), name: BRLoginManager.userInfoUpdateNotification, object: nil)
br_setupUI()
}
@ -53,6 +58,10 @@ class BRMineViewController: BRViewController {
self.navigationController?.setNavigationBarHidden(true, animated: true)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
BRLoginManager.manager.updateUserInfo(completer: nil)
}
}
@ -77,6 +86,14 @@ extension BRMineViewController {
}
extension BRMineViewController {
@objc private func userInfoUpdateNotification() {
self.tableView.reloadData()
}
}
//MARK: -------------- UITableViewDelegate UITableViewDataSource --------------
extension BRMineViewController: UITableViewDelegate, UITableViewDataSource {

View File

@ -9,12 +9,73 @@ import UIKit
class BRAboutUsHeaderView: UIView {
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
private lazy var appLogoView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "logo_icon_01"))
imageView.layer.cornerRadius = 8
imageView.layer.masksToBounds = true
imageView.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(handleLogoImageView))
imageView.addGestureRecognizer(tap)
return imageView
}()
private lazy var nameLabel: UILabel = {
let label = UILabel()
label.font = .fontBold(ofSize: 18)
label.textColor = .color1C1C1C()
label.text = kBRAPPName
return label
}()
private lazy var versionLabel: UILabel = {
let label = UILabel()
label.font = .fontRegular(ofSize: 12)
label.textColor = .color1C1C1C(alpha: 0.8)
label.text = "version:\(kBRAPPVersion)"
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
br_setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func handleLogoImageView() {
guard let url = URL(string: BRWebBaseURL) else { return }
UIApplication.shared.open(url)
}
}
extension BRAboutUsHeaderView {
private func br_setupUI() {
addSubview(appLogoView)
addSubview(nameLabel)
addSubview(versionLabel)
appLogoView.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalToSuperview().offset(50)
make.width.height.equalTo(100)
}
nameLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(appLogoView.snp.bottom).offset(10)
}
versionLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(nameLabel.snp.bottom).offset(5)
}
}
*/
}

View File

@ -38,6 +38,7 @@ class BRVideoDetailViewController: BRPlayerListViewController {
super.viewDidLoad()
self.delegate = self
self.dataSource = self
self.fd_interactivePopDisabled = true
self.requestDetailData()

View File

@ -0,0 +1,119 @@
//
// BRSearchViewController.swift
// BeeReel
//
// Created by on 2025/7/23.
//
import UIKit
class BRSearchViewController: BRViewController {
private lazy var viewModel = BRSearchViewModel()
private lazy var bgView: UIView = {
let view = UIImageView(image: UIImage(named: "搜索bg"))
return view
}()
private lazy var backButton: UIButton = {
let button = UIButton(type: .custom)
button.setImage(UIImage(named: "nav_back_icon_02"), for: .normal)
button.addTarget(self, action: #selector(handleNavBack), for: .touchUpInside)
return button
}()
private lazy var textView: BRSearchTextView = {
let view = BRSearchTextView()
view.didSearch = { [weak self] text in
guard let self = self else { return }
self.search(text: text)
}
return view
}()
private lazy var homeView: BRSearchHomeView = {
let view = BRSearchHomeView()
view.searchText = { [weak self] text in
self?.search(text: text)
}
view.viewModel = self.viewModel
return view
}()
private lazy var resultView: BRSearchResultView = {
let view = BRSearchResultView()
view.isHidden = true
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .colorFFFFFF()
br_setupUI()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(true, animated: true)
}
}
extension BRSearchViewController {
private func search(text: String) {
self.viewModel.addSearchRecord(text: text)
self.resultView.search(text: text)
if self.textView.text != text {
self.textView.text = text
}
if text.isEmpty {
self.homeView.isHidden = false
self.resultView.isHidden = true
} else {
self.homeView.isHidden = true
self.resultView.isHidden = false
}
}
}
extension BRSearchViewController {
private func br_setupUI() {
view.addSubview(bgView)
view.addSubview(backButton)
view.addSubview(textView)
view.addSubview(homeView)
view.addSubview(resultView)
bgView.snp.makeConstraints { make in
make.left.top.equalToSuperview()
}
backButton.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
make.top.equalToSuperview().offset(UIScreen.statusBarHeight + 16)
}
textView.snp.makeConstraints { make in
make.centerY.equalTo(backButton)
make.left.equalTo(backButton.snp.right).offset(10)
make.right.equalToSuperview().offset(-15)
}
homeView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.top.equalTo(textView.snp.bottom).offset(20)
}
resultView.snp.makeConstraints { make in
make.edges.equalTo(homeView)
}
}
}

View File

@ -0,0 +1,67 @@
//
// BRHotSearchTagCell.swift
// BeeReel
//
// Created by on 2025/7/23.
//
import UIKit
class BRHotSearchTagCell: BRCollectionViewCell {
var model: BRShortModel? {
didSet {
label.text = model?.name
}
}
var bgColor: UIColor? {
didSet {
self.contentView.backgroundColor = bgColor
}
}
var borderColor: UIColor? {
didSet {
self.contentView.layer.borderColor = borderColor?.cgColor
}
}
private lazy var hotIconImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "hot_icon_04"))
return imageView
}()
private lazy var label: UILabel = {
let label = UILabel()
label.font = .fontRegular(ofSize: 12)
label.textColor = .color000000()
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.contentView.layer.cornerRadius = 8
self.contentView.layer.masksToBounds = true
self.contentView.layer.borderWidth = 1
contentView.addSubview(hotIconImageView)
contentView.addSubview(label)
hotIconImageView.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.left.equalToSuperview().offset(9)
}
label.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.left.equalTo(hotIconImageView.snp.right).offset(4)
make.right.equalToSuperview().offset(-10)
}
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View File

@ -0,0 +1,115 @@
//
// BRHotSearchView.swift
// BeeReel
//
// Created by on 2025/7/23.
//
import UIKit
class BRHotSearchView: UIView {
var listArr: [BRShortModel] = [] {
didSet {
self.collectionView.reloadData()
}
}
var tagBgColorArr: [UIColor] = [.colorFFF3F5(), .colorE4FEFF(), .colorFFFDF1()]
var tagBorderColorArr: [UIColor] = [.colorFFD8DE(), .color82F8FF(), .colorFFEFA1()]
var searchText: ((_ text: String) -> Void)?
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .fontMedium(ofSize: 15)
label.textColor = .color1C1C1C()
label.text = "Most Searched".localized
return label
}()
private lazy var collectionViewLayout: AlignedCollectionViewFlowLayout = {
let layout = AlignedCollectionViewFlowLayout(horizontalAlignment: .left, verticalAlignment: .top)
layout.estimatedItemSize = .init(width: 30, height: 28)
layout.sectionInset = .init(top: 0, left: 15, bottom: 0, right: 15)
layout.minimumInteritemSpacing = 12
layout.minimumLineSpacing = 10
return layout
}()
private lazy var collectionView: BRCollectionView = {
let collectionView = BRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.addObserver(self, forKeyPath: "contentSize", context: nil)
collectionView.register(BRHotSearchTagCell.self, forCellWithReuseIdentifier: "tagCell")
return collectionView
}()
deinit {
self.collectionView.removeObserver(self, forKeyPath: "contentSize")
}
override init(frame: CGRect) {
super.init(frame: frame)
br_setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "contentSize" {
let height = self.collectionView.contentSize.height
self.collectionView.snp.updateConstraints { make in
make.height.equalTo(height + 1)
}
}
}
}
extension BRHotSearchView {
private func br_setupUI() {
addSubview(titleLabel)
addSubview(collectionView)
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
make.top.equalToSuperview()
}
collectionView.snp.makeConstraints { make in
make.left.equalToSuperview()
make.right.equalToSuperview()
make.top.equalToSuperview().offset(31)
make.height.equalTo(1)
make.bottom.equalToSuperview()
}
}
}
extension BRHotSearchView: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let colorIndex = indexPath.row % 3
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "tagCell", for: indexPath) as! BRHotSearchTagCell
cell.model = listArr[indexPath.row]
cell.bgColor = tagBgColorArr[colorIndex]
cell.borderColor = tagBorderColorArr[colorIndex]
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return listArr.count
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let text = self.listArr[indexPath.row].name, !text.isEmpty else { return }
self.searchText?(text)
}
}

View File

@ -0,0 +1,123 @@
//
// BRSearchHomeView.swift
// BeeReel
//
// Created by on 2025/7/23.
//
import UIKit
class BRSearchHomeView: UIView {
weak var viewModel: BRSearchViewModel? {
didSet {
recordView.listArr = viewModel?.recordList ?? []
viewModel?.addObserver(self, forKeyPath: "recordList", context: nil)
updateLayout()
}
}
var searchText: ((_ text: String) -> Void)?
private lazy var scrollView: BRScrollView = {
let scrollView = BRScrollView()
return scrollView
}()
private lazy var stackView: UIStackView = {
let view = UIStackView()
view.axis = .vertical
view.spacing = 30
return view
}()
private lazy var recordView: BRSearchRecordView = {
let view = BRSearchRecordView()
view.clickCleanButton = { [weak self] in
self?.viewModel?.clearSearchRecord()
}
view.searchText = { [weak self] text in
self?.searchText?(text)
}
return view
}()
private lazy var hotView: BRHotSearchView = {
let view = BRHotSearchView()
view.searchText = { [weak self] text in
self?.searchText?(text)
}
return view
}()
deinit {
self.viewModel?.removeObserver(self, forKeyPath: "recordList")
}
override init(frame: CGRect) {
super.init(frame: frame)
br_setupUI()
requestHotSearch()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "recordList" {
recordView.listArr = viewModel?.recordList ?? []
updateLayout()
}
}
private func updateLayout() {
stackView.br_removeAllArrangedSubview()
if self.viewModel?.recordList.isEmpty == false {
stackView.addArrangedSubview(recordView)
}
if self.viewModel?.hotSearchList.isEmpty == false {
stackView.addArrangedSubview(hotView)
}
}
}
extension BRSearchHomeView {
private func br_setupUI() {
addSubview(scrollView)
scrollView.addSubview(stackView)
scrollView.snp.makeConstraints { make in
make.left.right.top.equalToSuperview()
make.bottom.equalToSuperview()
}
stackView.snp.makeConstraints { make in
make.left.top.equalToSuperview()
make.width.equalTo(UIScreen.width)
make.bottom.equalToSuperview().offset(-(UIScreen.tabbarSafeBottomMargin + 10))
}
}
}
extension BRSearchHomeView {
private func requestHotSearch() {
BRHomeAPI.requestHotSearchList { [weak self] list in
guard let self = self else { return }
guard let list = list else { return }
self.viewModel?.hotSearchList = list
self.hotView.listArr = list
self.updateLayout()
}
}
}

View File

@ -0,0 +1,45 @@
//
// BRSearchRecordTagCell.swift
// BeeReel
//
// Created by on 2025/7/23.
//
import UIKit
class BRSearchRecordTagCell: BRCollectionViewCell {
var text: String? {
didSet {
label.text = text
}
}
private lazy var label: UILabel = {
let label = UILabel()
label.font = .fontRegular(ofSize: 12)
label.textColor = .color1C1C1C()
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.layer.cornerRadius = 8
contentView.layer.masksToBounds = true
contentView.backgroundColor = .colorF2F2F2()
contentView.addSubview(label)
label.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.left.equalToSuperview().offset(10)
make.centerX.equalToSuperview()
}
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View File

@ -0,0 +1,130 @@
//
// BRSearchRecordView.swift
// BeeReel
//
// Created by on 2025/7/23.
//
import UIKit
class BRSearchRecordView: UIView {
var listArr: [String] = [] {
didSet {
collectionView.reloadData()
}
}
var clickCleanButton: (() -> Void)?
var searchText: ((_ text: String) -> Void)?
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .fontMedium(ofSize: 13)
label.textColor = .color777777()
label.text = "Last Searched".localized
return label
}()
private lazy var collectionViewLayout: AlignedCollectionViewFlowLayout = {
let layout = AlignedCollectionViewFlowLayout(horizontalAlignment: .left, verticalAlignment: .top)
layout.estimatedItemSize = .init(width: 30, height: 28)
layout.sectionInset = .init(top: 0, left: 15, bottom: 0, right: 0)
layout.minimumInteritemSpacing = 10
layout.minimumLineSpacing = 10
return layout
}()
private lazy var collectionView: BRCollectionView = {
let collectionView = BRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.addObserver(self, forKeyPath: "contentSize", context: nil)
collectionView.register(BRSearchRecordTagCell.self, forCellWithReuseIdentifier: "tagCell")
return collectionView
}()
private lazy var deleteButton: UIButton = {
let button = UIButton(type: .custom)
button.setImage(UIImage(named: "delete_icon_01"), for: .normal)
button.setContentHuggingPriority(.required, for: .horizontal)
button.setContentCompressionResistancePriority(.required, for: .horizontal)
button.addTarget(self, action: #selector(handleDeleteButton), for: .touchUpInside)
return button
}()
deinit {
self.collectionView.removeObserver(self, forKeyPath: "contentSize")
}
override init(frame: CGRect) {
super.init(frame: frame)
br_setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func handleDeleteButton() {
self.clickCleanButton?()
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "contentSize" {
let height = self.collectionView.contentSize.height
self.collectionView.snp.updateConstraints { make in
make.height.equalTo(height + 1)
}
}
}
}
extension BRSearchRecordView {
private func br_setupUI() {
addSubview(titleLabel)
addSubview(collectionView)
addSubview(deleteButton)
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
make.top.equalToSuperview()
}
collectionView.snp.makeConstraints { make in
make.left.equalToSuperview()
make.top.equalToSuperview().offset(28)
make.right.equalTo(deleteButton.snp.left).offset(-15)
make.height.equalTo(1)
make.bottom.equalToSuperview()
}
deleteButton.snp.makeConstraints { make in
make.right.equalToSuperview().offset(-15)
make.top.equalToSuperview().offset(33)
}
}
}
//MARK: -------------- UICollectionViewDelegate UICollectionViewDataSource --------------
extension BRSearchRecordView: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "tagCell", for: indexPath) as! BRSearchRecordTagCell
cell.text = listArr[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return listArr.count
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.searchText?(listArr[indexPath.row])
}
}

View File

@ -0,0 +1,145 @@
//
// BRSearchResultCell.swift
// BeeReel
//
// Created by on 2025/7/23.
//
import UIKit
class BRSearchResultCell: BRCollectionViewCell {
var highlightText: String?
var model: BRShortModel? {
didSet {
coverView.br_setImage(url: model?.image_url)
descLabel.text = model?.br_description
hotView.setNeedsUpdateConfiguration()
categoryLabel.text = model?.category?.first
let name = model?.name ?? ""
let ranges = name.br_range(of: highlightText ?? "")
let nameString = NSMutableAttributedString(string: name)
ranges.forEach {
nameString.yy_setColor(.colorFF7489(), range: $0)
}
nameLabel.attributedText = nameString
}
}
private lazy var bgView: UIView = {
let view = UIImageView(image: UIImage(named: "Vector"))
return view
}()
private lazy var coverView: BRImageView = {
let view = BRImageView()
view.layer.cornerRadius = 8
view.layer.masksToBounds = true
return view
}()
private lazy var nameLabel: UILabel = {
let label = UILabel()
label.font = .fontMedium(ofSize: 14)
label.textColor = .color1C1C1C()
return label
}()
private lazy var categoryLabel: UILabel = {
let label = UILabel()
label.font = .fontRegular(ofSize: 10)
label.textColor = .color899D00()
return label
}()
private lazy var descLabel: UILabel = {
let label = UILabel()
label.font = .fontRegular(ofSize: 10)
label.textColor = .color777777()
label.numberOfLines = 2
return label
}()
private lazy var hotView: UIButton = {
var config = UIButton.Configuration.plain()
config.image = UIImage(named: "hot_icon_02")
config.imagePadding = 2
config.contentInsets = .init(top: 0, leading: 0, bottom: 0, trailing: 0)
let button = UIButton(configuration: config)
button.configurationUpdateHandler = { [weak self] button in
guard let self = self else { return }
let count = model?.watch_total ?? 0
var str = "\(count)"
if count > 1000 {
let num = NSNumber(value: Float(count) / 1000)
str = num.br_toString(maximumFractionDigits: 1) + "k"
}
button.configuration?.attributedTitle = AttributedString.br_createAttributedString(string: str, color: .colorFF7489(), font: .fontRegular(ofSize: 10))
}
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
br_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension BRSearchResultCell {
private func br_setupUI() {
contentView.addSubview(bgView)
bgView.addSubview(coverView)
bgView.addSubview(nameLabel)
bgView.addSubview(categoryLabel)
bgView.addSubview(descLabel)
bgView.addSubview(hotView)
bgView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
coverView.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.left.equalToSuperview().offset(7)
make.width.equalTo(90)
make.height.equalTo(112)
}
nameLabel.snp.makeConstraints { make in
make.left.equalTo(coverView.snp.right).offset(20)
make.top.equalToSuperview().offset(16)
make.right.lessThanOrEqualToSuperview().offset(-10)
}
categoryLabel.snp.makeConstraints { make in
make.left.equalTo(nameLabel)
make.top.equalToSuperview().offset(41)
}
descLabel.snp.makeConstraints { make in
make.left.equalTo(nameLabel)
make.top.equalToSuperview().offset(55)
make.right.lessThanOrEqualToSuperview().offset(-10)
}
hotView.snp.makeConstraints { make in
make.left.equalTo(nameLabel)
make.bottom.equalToSuperview().offset(-16)
}
}
}

View File

@ -0,0 +1,112 @@
//
// BRSearchResultView.swift
// BeeReel
//
// Created by on 2025/7/23.
//
import UIKit
class BRSearchResultView: UIView {
private var searchText: String = ""
private var listArr: [BRShortModel] = []
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.textColor = .color1C1C1C()
label.font = .fontMedium(ofSize: 15)
label.text = "Search Results".localized
return label
}()
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
let layout = UICollectionViewFlowLayout()
layout.itemSize = .init(width: UIScreen.width - 30, height: 126)
layout.minimumLineSpacing = 10
layout.sectionInset = .init(top: 0, left: 15, bottom: 0, right: 15)
return layout
}()
private lazy var collectionView: BRCollectionView = {
let collectionView = BRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.contentInset = .init(top: 0, left: 0, bottom: UIScreen.tabbarSafeBottomMargin + 10, right: 0)
collectionView.register(BRSearchResultCell.self, forCellWithReuseIdentifier: "cell")
return collectionView
}()
override init(frame: CGRect) {
super.init(frame: frame)
br_setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func search(text: String) {
self.searchText = text
BRAppTool.keyWindow?.endEditing(true)
if text.isEmpty {
self.listArr.removeAll()
self.collectionView.reloadData()
} else {
requestSearch(text: text)
}
}
}
extension BRSearchResultView {
private func br_setupUI() {
addSubview(titleLabel)
addSubview(collectionView)
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
make.top.equalToSuperview()
}
collectionView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.top.equalTo(titleLabel.snp.bottom).offset(10)
}
}
}
extension BRSearchResultView: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! BRSearchResultCell
cell.highlightText = self.searchText
cell.model = self.listArr[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
self.listArr.count
}
}
extension BRSearchResultView {
private func requestSearch(text: String) {
BRHomeAPI.requestSearch(text: searchText) { [weak self] list in
guard let self = self else { return }
guard text == self.searchText else { return }
self.listArr = list ?? []
self.collectionView.reloadData()
}
}
}

View File

@ -0,0 +1,76 @@
//
// BRSearchTextView.swift
// BeeReel
//
// Created by on 2025/7/23.
//
import UIKit
class BRSearchTextView: UIView {
override var intrinsicContentSize: CGSize {
return .init(width: UIScreen.width, height: 40)
}
var didSearch: ((_ text: String) -> Void)?
var text: String? {
get {
return textField.text
}
set {
textField.text = newValue
}
}
private lazy var textField: UITextField = {
let placeholder = NSMutableAttributedString(string: "Hot Picks Waiting for You".localized)
placeholder.yy_font = .fontRegular(ofSize: 14)
placeholder.yy_color = .colorD3D3D3()
let textField = UITextField()
textField.attributedPlaceholder = placeholder
textField.font = .fontRegular(ofSize: 14)
textField.textColor = .color1C1C1C()
textField.returnKeyType = .search
textField.delegate = self
return textField
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.layer.cornerRadius = 20
self.layer.masksToBounds = true
self.layer.borderColor = UIColor.color1C1C1C().cgColor
self.layer.borderWidth = 1
self.backgroundColor = .colorFFFFFF()
self.addSubview(textField)
textField.snp.makeConstraints { make in
make.left.equalToSuperview().offset(20)
make.right.equalToSuperview().offset(-20)
make.top.bottom.equalToSuperview()
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
//MARK: -------------- UITextFieldDelegate --------------
extension BRSearchTextView: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
if let text = textField.text {
self.didSearch?(text)
}
return true
}
}

View File

@ -0,0 +1,44 @@
//
// BRSearchViewModel.swift
// BeeReel
//
// Created by on 2025/7/23.
//
import UIKit
class BRSearchViewModel: NSObject {
static let searchRecordUserDefaultKey = "BRSearchViewModel.searchRecordUserDefaultKey"
///
@objc dynamic private(set) var recordList: [String] = (UserDefaults.standard.object(forKey: BRSearchViewModel.searchRecordUserDefaultKey) as? [String]) ?? []
var hotSearchList: [BRShortModel] = []
func addSearchRecord(text: String) {
guard !text.isEmpty else { return }
var list = recordList
for (index, value) in list.enumerated() {
if value == text {
list.remove(at: index)
break
}
}
list.insert(text, at: 0)
if list.count > 10 {
list.removeLast()
}
recordList = list
UserDefaults.standard.set(list, forKey: BRSearchViewModel.searchRecordUserDefaultKey)
}
func clearSearchRecord() {
recordList.removeAll()
UserDefaults.standard.set(recordList, forKey: BRSearchViewModel.searchRecordUserDefaultKey)
}
}

View File

@ -14,6 +14,8 @@ extension AppDelegate {
//
MJRefreshConfig.default.languageCode = BRLocalizedManager.manager.mjLocalizedKey
}
}

View File

@ -33,70 +33,6 @@ class BRLoginManager {
UserDefaults.br_setObject(token, forKey: kBRLoginTokenDefaultsKey)
}
func openLogin(finishHandle: (() -> Void)? = nil) {
// let view = VPLoginContentView()
// view.loginFinishBlock = finishHandle
// view.present(in: nil)
}
func login(type: LoginType, presentingViewController: UIViewController?, completer: ((_ isFinish: Bool) -> Void)?) {
// switch type {
// case .apple:
// appleSignLogin { [weak self] model in
// self?.requestThirdLogin(thirdSignModel: model, completer: completer)
// }
//
// case .faceBook:
// facebookLogin(presentingViewController: presentingViewController) { [weak self] model in
// self?.requestThirdLogin(thirdSignModel: model, completer: completer)
// }
// default:
// completer?(false)
// }
}
///退
func logout(completer: ((_ isFinish: Bool) -> Void)?) {
// VPStatAPI.requestLeaveApp()
// VPUserAPI.requestLogout { [weak self] token in
// guard let self = self else { return }
// if let token = token {
// self.setLoginToken(token: token)
// self.userInfo?.is_tourist = true
// self.updateUserInfo(completer: nil)
// VPStatAPI.requestStatOnLine()
// VPStatAPI.requestEnterApp()
// completer?(true)
// NotificationCenter.default.post(name: VPLoginManager.userInfoUpdateNotification, object: nil)
// NotificationCenter.default.post(name: VPLoginManager.loginStateDidChangeNotification, object: nil)
// } else {
// completer?(false)
// }
// }
}
///
func deleteAccount(completer: ((_ isFinish: Bool) -> Void)?) {
// VPStatAPI.requestLeaveApp()
// VPUserAPI.requestDelete { [weak self] isFinish in
// guard let self = self else { return }
// if isFinish {
// self.setLoginToken(token: nil)
// self.userInfo?.is_tourist = true
// self.updateUserInfo(completer: nil)
// VPStatAPI.requestStatOnLine()
// VPStatAPI.requestEnterApp()
// completer?(true)
// NotificationCenter.default.post(name: VPLoginManager.userInfoUpdateNotification, object: nil)
// NotificationCenter.default.post(name: VPLoginManager.loginStateDidChangeNotification, object: nil)
// } else {
// completer?(false)
// }
// }
}
///
func updateUserInfo(completer: (() -> Void)?) {
@ -119,8 +55,6 @@ class BRLoginManager {
extension BRLoginManager {
///
@objc static let loginStateDidChangeNotification = NSNotification.Name(rawValue: "BRLoginManager.loginStateDidChangeNotification")
///
@objc static let userInfoUpdateNotification = NSNotification.Name(rawValue: "BRLoginManager.userInfoUpdateNotification")

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: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Binge in Minutes, Love for Hours@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Binge in Minutes, Love for Hours@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,44 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Vector@2x.png",
"idiom" : "universal",
"resizing" : {
"cap-insets" : {
"left" : 265,
"right" : 23
},
"center" : {
"mode" : "tile",
"width" : 1
},
"mode" : "3-part-horizontal"
},
"scale" : "2x"
},
{
"filename" : "Vector@3x.png",
"idiom" : "universal",
"resizing" : {
"cap-insets" : {
"left" : 398,
"right" : 34
},
"center" : {
"mode" : "tile",
"width" : 1
},
"mode" : "3-part-horizontal"
},
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 625 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 894 B

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: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

View File

@ -1,7 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@ -11,10 +13,35 @@
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="launch_screen_bg_image" translatesAutoresizingMaskIntoConstraints="NO" id="hT7-Oq-bJG">
<rect key="frame" x="0.0" y="0.0" width="393" height="812"/>
</imageView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="launch_screen_logo" translatesAutoresizingMaskIntoConstraints="NO" id="Z0n-Iq-hXz">
<rect key="frame" x="146.66666666666666" y="184" width="100" height="100"/>
</imageView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="launch_screen_mark" translatesAutoresizingMaskIntoConstraints="NO" id="PKP-Wp-5ij">
<rect key="frame" x="90" y="296" width="17" height="20"/>
</imageView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="launch_screen_text" translatesAutoresizingMaskIntoConstraints="NO" id="RC7-kh-wFK">
<rect key="frame" x="100.00000000000001" y="305" width="193.33333333333337" height="42.666666666666686"/>
</imageView>
</subviews>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="hT7-Oq-bJG" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" id="01Q-Rg-CU2"/>
<constraint firstItem="Z0n-Iq-hXz" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="0gz-Qt-gtK"/>
<constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="hT7-Oq-bJG" secondAttribute="trailing" id="8hs-wA-AyO"/>
<constraint firstItem="RC7-kh-wFK" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="SKU-aQ-5GE"/>
<constraint firstItem="RC7-kh-wFK" firstAttribute="top" secondItem="Z0n-Iq-hXz" secondAttribute="bottom" constant="21" id="TMS-rr-qCL"/>
<constraint firstItem="hT7-Oq-bJG" firstAttribute="top" secondItem="Z0n-Iq-hXz" secondAttribute="top" constant="-184" id="iDf-05-NHI"/>
<constraint firstItem="RC7-kh-wFK" firstAttribute="leading" secondItem="PKP-Wp-5ij" secondAttribute="leading" constant="10" id="khV-ML-M0f"/>
<constraint firstItem="PKP-Wp-5ij" firstAttribute="top" secondItem="RC7-kh-wFK" secondAttribute="top" constant="-9" id="uz3-8A-aS2"/>
<constraint firstItem="hT7-Oq-bJG" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="yZa-qG-DSc"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
@ -22,4 +49,10 @@
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="launch_screen_bg_image" width="375" height="812"/>
<image name="launch_screen_logo" width="100" height="100"/>
<image name="launch_screen_mark" width="17" height="20"/>
<image name="launch_screen_text" width="193.33332824707031" height="42.666667938232422"/>
</resources>
</document>

View File

@ -6,3 +6,4 @@
#import <YYText/YYTextWeakProxy.h>
#import <WMZPageController/WMZPageController.h>
#import "WMZBannerView.h"
#import <FDFullscreenPopGesture/UINavigationController+FDFullscreenPopGesture.h>

View File

@ -111,6 +111,39 @@
}
}
},
"Hot Picks Waiting for You" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Hot Picks Waiting for You"
}
}
}
},
"Last Searched" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Last Searched"
}
}
}
},
"Most Searched" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Most Searched"
}
}
}
},
"New Releases" : {
"extractionState" : "manual",
"localizations" : {
@ -144,6 +177,17 @@
}
}
},
"Search Results" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Search Results"
}
}
}
},
"Speed" : {
"extractionState" : "manual",
"localizations" : {
@ -167,7 +211,15 @@
}
},
"Third-party information sharing list" : {
"extractionState" : "manual"
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Third-party information sharing list"
}
}
}
},
"Top 10" : {
"extractionState" : "manual",

View File

@ -0,0 +1,432 @@
//
// AlignedCollectionViewFlowLayout.swift
//
// Created by Mischa Hildebrand on 12/04/2017.
// Copyright © 2017 Mischa Hildebrand.
//
// Licensed under the terms of the MIT license:
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// MARK: - 🦆 Type definitions
/// An abstract protocol that defines an alignment.
protocol Alignment {}
/// Defines an alignment for UI elements.
public enum HorizontalAlignment: Alignment {
case left
case justified
case right
}
/// Defines a vertical alignment for UI elements.
public enum VerticalAlignment: Alignment {
case top
case center
case bottom
}
/// Describes an axis with respect to which items can be aligned.
private struct AlignmentAxis<A: Alignment> {
/// Determines how items are aligned relative to the axis.
let alignment: A
/// Defines the position of the axis.
/// * If the `Alignment` is horizontal, the alignment axis is vertical and this is the position on the `x` axis.
/// * If the `Alignment` is vertical, the alignment axis is horizontal and this is the position on the `y` axis.
let position: CGFloat
}
/// A `UICollectionViewFlowLayout` subclass that gives you control
/// over the horizontal and vertical alignment of the cells.
/// You can use it to align the cells like words in a left- or right-aligned text
/// and you can specify how the cells are vertically aligned in their row.
open class AlignedCollectionViewFlowLayout: UICollectionViewFlowLayout {
// MARK: - 🔶 Properties
/// Determines how the cells are horizontally aligned in a row.
/// - Note: The default is `.justified`.
public var horizontalAlignment: HorizontalAlignment = .justified
/// Determines how the cells are vertically aligned in a row.
/// - Note: The default is `.center`.
public var verticalAlignment: VerticalAlignment = .center
/// The vertical axis with respect to which the cells are horizontally aligned.
/// For a `justified` alignment the alignment axis is not defined and this value is `nil`.
fileprivate var alignmentAxis: AlignmentAxis<HorizontalAlignment>? {
switch horizontalAlignment {
case .left:
return AlignmentAxis(alignment: HorizontalAlignment.left, position: sectionInset.left)
case .right:
guard let collectionViewWidth = collectionView?.frame.size.width else {
return nil
}
return AlignmentAxis(alignment: HorizontalAlignment.right, position: collectionViewWidth - sectionInset.right)
default:
return nil
}
}
/// The width of the area inside the collection view that can be filled with cells.
private var contentWidth: CGFloat? {
guard let collectionViewWidth = collectionView?.frame.size.width else {
return nil
}
return collectionViewWidth - sectionInset.left - sectionInset.right
}
// MARK: - 👶 Initialization
/// The designated initializer.
///
/// - Parameters:
/// - horizontalAlignment: Specifies how the cells are horizontally aligned in a row. --
/// (Default: `.justified`)
/// - verticalAlignment: Specified how the cells are vertically aligned in a row. --
/// (Default: `.center`)
public init(horizontalAlignment: HorizontalAlignment = .justified, verticalAlignment: VerticalAlignment = .center) {
super.init()
self.horizontalAlignment = horizontalAlignment
self.verticalAlignment = verticalAlignment
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
// MARK: - 🅾 Overrides
override open func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
// 💡 IDEA:
// The approach for computing a cell's frame is to create a rectangle that covers the current line.
// Then we check if the preceding cell's frame intersects with this rectangle.
// If it does, the current item is not the first item in the line. Otherwise it is.
// (Vice-versa for right-aligned cells.)
//
// +---------+----------------------------------------------------------------+---------+
// | | | |
// | | +------------+ | |
// | | | | | |
// | section |- - -|- - - - - - |- - - - +---------------------+ - - - - - - -| section |
// | inset | |intersection| | | line rect | inset |
// | |- - -|- - - - - - |- - - - +---------------------+ - - - - - - -| |
// | (left) | | | current item | (right) |
// | | +------------+ | |
// | | previous item | |
// +---------+----------------------------------------------------------------+---------+
//
// We need this rather complicated approach because the first item in a line
// is not always left-aligned and the last item in a line is not always right-aligned:
// If there is only one item in a line UICollectionViewFlowLayout will center it.
// We may not change the original layout attributes or UICollectionViewFlowLayout might complain.
guard let layoutAttributes = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes else {
return nil
}
// For a justified layout there's nothing to do here
// as UICollectionViewFlowLayout justifies the items in a line by default.
if horizontalAlignment != .justified {
layoutAttributes.alignHorizontally(collectionViewLayout: self)
}
// For a vertically centered layout there's nothing to do here
// as UICollectionViewFlowLayout center-aligns the items in a line by default.
if verticalAlignment != .center {
layoutAttributes.alignVertically(collectionViewLayout: self)
}
return layoutAttributes
}
override open func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
// We may not change the original layout attributes or UICollectionViewFlowLayout might complain.
let layoutAttributesObjects = copy(super.layoutAttributesForElements(in: rect))
layoutAttributesObjects?.forEach({ (layoutAttributes) in
setFrame(forLayoutAttributes: layoutAttributes)
})
return layoutAttributesObjects
}
// MARK: - 👷 Private layout helpers
/// Sets the frame for the passed layout attributes object by calling the `layoutAttributesForItem(at:)` function.
private func setFrame(forLayoutAttributes layoutAttributes: UICollectionViewLayoutAttributes) {
if layoutAttributes.representedElementCategory == .cell { // Do not modify header views etc.
let indexPath = layoutAttributes.indexPath
if let newFrame = layoutAttributesForItem(at: indexPath)?.frame {
layoutAttributes.frame = newFrame
}
}
}
/// A function to access the `super` implementation of `layoutAttributesForItem(at:)` externally.
///
/// - Parameter indexPath: The index path of the item for which to return the layout attributes.
/// - Returns: The unmodified layout attributes for the item at the specified index path
/// as computed by `UICollectionViewFlowLayout`.
fileprivate func originalLayoutAttribute(forItemAt indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return super.layoutAttributesForItem(at: indexPath)
}
/// Determines if the `firstItemAttributes`' frame is in the same line
/// as the `secondItemAttributes`' frame.
///
/// - Parameters:
/// - firstItemAttributes: The first layout attributes object to be compared.
/// - secondItemAttributes: The second layout attributes object to be compared.
/// - Returns: `true` if the frames of the two layout attributes are in the same line, else `false`.
/// `false` is also returned when the layout's `collectionView` property is `nil`.
fileprivate func isFrame(for firstItemAttributes: UICollectionViewLayoutAttributes, inSameLineAsFrameFor secondItemAttributes: UICollectionViewLayoutAttributes) -> Bool {
guard let lineWidth = contentWidth else {
return false
}
let firstItemFrame = firstItemAttributes.frame
let lineFrame = CGRect(x: sectionInset.left,
y: firstItemFrame.origin.y,
width: lineWidth,
height: firstItemFrame.size.height)
return lineFrame.intersects(secondItemAttributes.frame)
}
/// Determines the layout attributes objects for all items displayed in the same line as the item
/// represented by the passed `layoutAttributes` object.
///
/// - Parameter layoutAttributes: The layout attributed that represents the reference item.
/// - Returns: The layout attributes objects representing all other items in the same line.
/// The passed `layoutAttributes` object itself is always contained in the returned array.
fileprivate func layoutAttributes(forItemsInLineWith layoutAttributes: UICollectionViewLayoutAttributes) -> [UICollectionViewLayoutAttributes] {
guard let lineWidth = contentWidth else {
return [layoutAttributes]
}
var lineFrame = layoutAttributes.frame
lineFrame.origin.x = sectionInset.left
lineFrame.size.width = lineWidth
return super.layoutAttributesForElements(in: lineFrame) ?? []
}
/// Copmutes the alignment axis with which to align the items represented by the `layoutAttributes` objects vertically.
///
/// - Parameter layoutAttributes: The layout attributes objects to be vertically aligned.
/// - Returns: The axis with respect to which the layout attributes can be aligned
/// or `nil` if the `layoutAttributes` array is empty.
private func verticalAlignmentAxisForLine(with layoutAttributes: [UICollectionViewLayoutAttributes]) -> AlignmentAxis<VerticalAlignment>? {
guard let firstAttribute = layoutAttributes.first else {
return nil
}
switch verticalAlignment {
case .top:
let minY = layoutAttributes.reduce(CGFloat.greatestFiniteMagnitude) { min($0, $1.frame.minY) }
return AlignmentAxis(alignment: .top, position: minY)
case .bottom:
let maxY = layoutAttributes.reduce(0) { max($0, $1.frame.maxY) }
return AlignmentAxis(alignment: .bottom, position: maxY)
default:
let centerY = firstAttribute.center.y
return AlignmentAxis(alignment: .center, position: centerY)
}
}
/// Computes the axis with which to align the item represented by the `currentLayoutAttributes` vertically.
///
/// - Parameter currentLayoutAttributes: The layout attributes representing the item to be vertically aligned.
/// - Returns: The axis with respect to which the item can be aligned.
fileprivate func verticalAlignmentAxis(for currentLayoutAttributes: UICollectionViewLayoutAttributes) -> AlignmentAxis<VerticalAlignment>? {
let layoutAttributesInLine = layoutAttributes(forItemsInLineWith: currentLayoutAttributes)
// It's okay to force-unwrap here because we pass a non-empty array.
return verticalAlignmentAxisForLine(with: layoutAttributesInLine)
}
/// Creates a deep copy of the passed array by copying all its items.
///
/// - Parameter layoutAttributesArray: The array to be copied.
/// - Returns: A deep copy of the passed array.
private func copy(_ layoutAttributesArray: [UICollectionViewLayoutAttributes]?) -> [UICollectionViewLayoutAttributes]? {
return layoutAttributesArray?.map{ $0.copy() } as? [UICollectionViewLayoutAttributes]
}
}
// MARK: - 👷 Layout attributes helpers
fileprivate extension UICollectionViewLayoutAttributes {
private var currentSection: Int {
return indexPath.section
}
private var currentItem: Int {
return indexPath.item
}
/// The index path for the item preceding the item represented by this layout attributes object.
private var precedingIndexPath: IndexPath {
return IndexPath(item: currentItem - 1, section: currentSection)
}
/// The index path for the item following the item represented by this layout attributes object.
private var followingIndexPath: IndexPath {
return IndexPath(item: currentItem + 1, section: currentSection)
}
/// Checks if the item represetend by this layout attributes object is the first item in the line.
///
/// - Parameter collectionViewLayout: The layout for which to perform the check.
/// - Returns: `true` if the represented item is the first item in the line, else `false`.
func isRepresentingFirstItemInLine(collectionViewLayout: AlignedCollectionViewFlowLayout) -> Bool {
if currentItem <= 0 {
return true
}
else {
if let layoutAttributesForPrecedingItem = collectionViewLayout.originalLayoutAttribute(forItemAt: precedingIndexPath) {
return !collectionViewLayout.isFrame(for: self, inSameLineAsFrameFor: layoutAttributesForPrecedingItem)
}
else {
return true
}
}
}
/// Checks if the item represetend by this layout attributes object is the last item in the line.
///
/// - Parameter collectionViewLayout: The layout for which to perform the check.
/// - Returns: `true` if the represented item is the last item in the line, else `false`.
func isRepresentingLastItemInLine(collectionViewLayout: AlignedCollectionViewFlowLayout) -> Bool {
guard let itemCount = collectionViewLayout.collectionView?.numberOfItems(inSection: currentSection) else {
return false
}
if currentItem >= itemCount - 1 {
return true
}
else {
if let layoutAttributesForFollowingItem = collectionViewLayout.originalLayoutAttribute(forItemAt: followingIndexPath) {
return !collectionViewLayout.isFrame(for: self, inSameLineAsFrameFor: layoutAttributesForFollowingItem)
}
else {
return true
}
}
}
/// Moves the layout attributes object's frame so that it is aligned horizontally with the alignment axis.
func align(toAlignmentAxis alignmentAxis: AlignmentAxis<HorizontalAlignment>) {
switch alignmentAxis.alignment {
case .left:
frame.origin.x = alignmentAxis.position
case .right:
frame.origin.x = alignmentAxis.position - frame.size.width
default:
break
}
}
/// Moves the layout attributes object's frame so that it is aligned vertically with the alignment axis.
func align(toAlignmentAxis alignmentAxis: AlignmentAxis<VerticalAlignment>) {
switch alignmentAxis.alignment {
case .top:
frame.origin.y = alignmentAxis.position
case .bottom:
frame.origin.y = alignmentAxis.position - frame.size.height
default:
center.y = alignmentAxis.position
}
}
/// Positions the frame right of the preceding item's frame, leaving a spacing between the frames
/// as defined by the collection view layout's `minimumInteritemSpacing`.
///
/// - Parameter collectionViewLayout: The layout on which to perfom the calculations.
private func alignToPrecedingItem(collectionViewLayout: AlignedCollectionViewFlowLayout) {
let itemSpacing = collectionViewLayout.minimumInteritemSpacing
if let precedingItemAttributes = collectionViewLayout.layoutAttributesForItem(at: precedingIndexPath) {
frame.origin.x = precedingItemAttributes.frame.maxX + itemSpacing
}
}
/// Positions the frame left of the following item's frame, leaving a spacing between the frames
/// as defined by the collection view layout's `minimumInteritemSpacing`.
///
/// - Parameter collectionViewLayout: The layout on which to perfom the calculations.
private func alignToFollowingItem(collectionViewLayout: AlignedCollectionViewFlowLayout) {
let itemSpacing = collectionViewLayout.minimumInteritemSpacing
if let followingItemAttributes = collectionViewLayout.layoutAttributesForItem(at: followingIndexPath) {
frame.origin.x = followingItemAttributes.frame.minX - itemSpacing - frame.size.width
}
}
/// Aligns the frame horizontally as specified by the collection view layout's `horizontalAlignment`.
///
/// - Parameters:
/// - collectionViewLayout: The layout providing the alignment information.
func alignHorizontally(collectionViewLayout: AlignedCollectionViewFlowLayout) {
guard let alignmentAxis = collectionViewLayout.alignmentAxis else {
return
}
switch collectionViewLayout.horizontalAlignment {
case .left:
if isRepresentingFirstItemInLine(collectionViewLayout: collectionViewLayout) {
align(toAlignmentAxis: alignmentAxis)
} else {
alignToPrecedingItem(collectionViewLayout: collectionViewLayout)
}
case .right:
if isRepresentingLastItemInLine(collectionViewLayout: collectionViewLayout) {
align(toAlignmentAxis: alignmentAxis)
} else {
alignToFollowingItem(collectionViewLayout: collectionViewLayout)
}
default:
return
}
}
/// Aligns the frame vertically as specified by the collection view layout's `verticalAlignment`.
///
/// - Parameter collectionViewLayout: The layout providing the alignment information.
func alignVertically(collectionViewLayout: AlignedCollectionViewFlowLayout) {
guard let alignmentAxis = collectionViewLayout.verticalAlignmentAxis(for: self) else {
return
}
align(toAlignmentAxis: alignmentAxis)
}
}

View File

@ -15,6 +15,6 @@
/**
app后
*/
+ (nonnull NSString *)systemUUID;
+ (nonnull NSString *)idfv;
@end

View File

@ -23,7 +23,7 @@ static NSString *const uuidKey = @"com.JXUUID";
uuid = [PDKeyChain objectForKey:uuidKey];
if (uuid && uuid.length > 0) {
} else {
uuid = [[NSUUID UUID] UUIDString];
uuid = [self idfv];
[PDKeyChain setObject:uuid forKey:uuidKey];
}
});
@ -31,15 +31,10 @@ static NSString *const uuidKey = @"com.JXUUID";
}
+ (nonnull NSString *)idfa
{
static NSString *idfa;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
idfa = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
});
return idfa;
return [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
}
+ (nonnull NSString *)systemUUID
+ (nonnull NSString *)idfv
{
return [UIDevice currentDevice].identifierForVendor.UUIDString;
}

View File

@ -21,7 +21,6 @@ target 'BeeReel' do
pod 'Kingfisher' #图片加载
pod 'SnapKit' #布局
pod 'Toast' #吐司提示
# pod 'YYKit' #工具类
pod 'SJVideoPlayer' #播放器
pod 'SJMediaCacheServer' #播放器缓存
pod 'WMZPageController' #分页控制器
@ -30,5 +29,6 @@ target 'BeeReel' do
pod 'FSPagerView' #banner
pod 'MJRefresh' #刷新控件
pod 'HWPanModal' #底部弹出控制器
pod 'FDFullscreenPopGesture' #全屏手势返回
end