首页开发,tabbar样式开发,网络框架搭建

This commit is contained in:
zjx 2025-05-20 18:30:08 +08:00
parent 54b5f08e23
commit a687f01794
52 changed files with 3020 additions and 34 deletions

View File

@ -27,5 +27,6 @@ target 'Veloria' do
pod 'HWPanModal' #底部弹出控制器
pod 'Kingfisher' #图片加载
pod "ESTabBarController-swift"
pod 'WMZPageController' #分页控制器
end

View File

@ -16,6 +16,7 @@ PODS:
- SmartCodable/Core (5.0.9)
- SnapKit (5.7.1)
- Toast (4.1.1)
- WMZPageController (1.5.5)
- YYKit (1.0.9):
- YYKit/no-arc (= 1.0.9)
- YYKit/no-arc (1.0.9)
@ -33,6 +34,7 @@ DEPENDENCIES:
- SmartCodable
- SnapKit
- Toast
- WMZPageController
- YYKit
- ZFPlayer/AVPlayer
@ -49,6 +51,7 @@ SPEC REPOS:
- SmartCodable
- SnapKit
- Toast
- WMZPageController
- YYKit
- ZFPlayer
@ -64,9 +67,10 @@ SPEC CHECKSUMS:
SmartCodable: 68b3598438181a938eed8ee5623e58ef3e3ea443
SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a
Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e
WMZPageController: 87dd82d1e3528cd362de19b9a74fd6890d6e1906
YYKit: 7cda43304a8dc3696c449041e2cb3107b4e236e7
ZFPlayer: 5cf39e8d9f0c2394a014b0db4767b5b5a6bffe13
PODFILE CHECKSUM: 41b6b69056545708f3956ded0b47fda298f175b8
PODFILE CHECKSUM: e5cac55fd92698becba7d142dc17ec724cd6313f
COCOAPODS: 1.16.2

View File

@ -33,6 +33,36 @@
1B056E772DDB3641007EE38D /* VPTabBarItemNormalVew.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B056E762DDB3641007EE38D /* VPTabBarItemNormalVew.swift */; };
1B056E792DDB365A007EE38D /* VPTabBarItemSelectedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B056E782DDB365A007EE38D /* VPTabBarItemSelectedView.swift */; };
1B056E7B2DDB37BA007EE38D /* VPGradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B056E7A2DDB37BA007EE38D /* VPGradientView.swift */; };
BF0FA6D52DDC5B5D00C9E5F2 /* VPApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA6D42DDC5B5D00C9E5F2 /* VPApi.swift */; };
BF0FA6D72DDC5BE100C9E5F2 /* VPURLPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA6D62DDC5BE100C9E5F2 /* VPURLPath.swift */; };
BF0FA6DA2DDC5CB600C9E5F2 /* VPLoginManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA6D92DDC5CB600C9E5F2 /* VPLoginManager.swift */; };
BF0FA6DC2DDC5CD700C9E5F2 /* VPTokenModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA6DB2DDC5CD700C9E5F2 /* VPTokenModel.swift */; };
BF0FA6DF2DDC5E4D00C9E5F2 /* String+VPAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA6DE2DDC5E4D00C9E5F2 /* String+VPAdd.swift */; };
BF0FA6EE2DDC5F8700C9E5F2 /* JXUUID.m in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA6EA2DDC5F8700C9E5F2 /* JXUUID.m */; };
BF0FA6EF2DDC5F8700C9E5F2 /* PDKeyChain.m in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA6EC2DDC5F8700C9E5F2 /* PDKeyChain.m */; };
BF0FA6F12DDC600200C9E5F2 /* VPNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA6F02DDC600200C9E5F2 /* VPNetwork.swift */; };
BF0FA6F42DDC604500C9E5F2 /* VPHUD.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA6F32DDC604500C9E5F2 /* VPHUD.swift */; };
BF0FA6F62DDC616300C9E5F2 /* VPToast.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA6F52DDC616300C9E5F2 /* VPToast.swift */; };
BF0FA6F92DDC64E700C9E5F2 /* VPHomeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA6F82DDC64E700C9E5F2 /* VPHomeAPI.swift */; };
BF0FA6FC2DDC657500C9E5F2 /* VPHomeDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA6FB2DDC657500C9E5F2 /* VPHomeDataModel.swift */; };
BF0FA7002DDC665300C9E5F2 /* VPShortModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA6FF2DDC665300C9E5F2 /* VPShortModel.swift */; };
BF0FA7022DDC667C00C9E5F2 /* VPVideoInfoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7012DDC667C00C9E5F2 /* VPVideoInfoModel.swift */; };
BF0FA7052DDC67AC00C9E5F2 /* VPHomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7042DDC67AC00C9E5F2 /* VPHomeViewModel.swift */; };
BF0FA7072DDC687C00C9E5F2 /* VPHomeDataItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7062DDC687C00C9E5F2 /* VPHomeDataItem.swift */; };
BF0FA70A2DDC69C800C9E5F2 /* VPTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7092DDC69C800C9E5F2 /* VPTableViewCell.swift */; };
BF0FA70C2DDC6A3800C9E5F2 /* VPHomeBannerContentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA70B2DDC6A3800C9E5F2 /* VPHomeBannerContentCell.swift */; };
BF0FA70E2DDC6ACC00C9E5F2 /* VPHomeItemContentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA70D2DDC6ACC00C9E5F2 /* VPHomeItemContentCell.swift */; };
BF0FA7102DDC6CA200C9E5F2 /* VPListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA70F2DDC6CA200C9E5F2 /* VPListModel.swift */; };
BF0FA7122DDC6D2C00C9E5F2 /* VPHomeModuleItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7112DDC6D2C00C9E5F2 /* VPHomeModuleItem.swift */; };
BF0FA7162DDC78FF00C9E5F2 /* ZKCycleScrollViewFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7142DDC78FF00C9E5F2 /* ZKCycleScrollViewFlowLayout.swift */; };
BF0FA7172DDC78FF00C9E5F2 /* ZKCycleScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7132DDC78FF00C9E5F2 /* ZKCycleScrollView.swift */; };
BF0FA7192DDC7F4900C9E5F2 /* VPHomeBannerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7182DDC7F4900C9E5F2 /* VPHomeBannerCell.swift */; };
BF0FA71B2DDC7FF200C9E5F2 /* VPImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA71A2DDC7FF200C9E5F2 /* VPImageView.swift */; };
BF0FA71D2DDC807200C9E5F2 /* UIImageView+VPAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA71C2DDC807200C9E5F2 /* UIImageView+VPAdd.swift */; };
BF0FA7202DDC83AE00C9E5F2 /* JXButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA71E2DDC83AE00C9E5F2 /* JXButton.swift */; };
BF0FA7222DDC859D00C9E5F2 /* NSNumber+VPAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7212DDC859D00C9E5F2 /* NSNumber+VPAdd.swift */; };
BF0FA7242DDC888F00C9E5F2 /* VPHomeRecommandContentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7232DDC888F00C9E5F2 /* VPHomeRecommandContentCell.swift */; };
BF0FA7262DDC8F7600C9E5F2 /* VPCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7252DDC8F7600C9E5F2 /* VPCollectionView.swift */; };
F939C04AD4003BA127F15C28 /* Pods_Veloria.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34F57E87E765BF8D72A43DCA /* Pods_Veloria.framework */; };
/* End PBXBuildFile section */
@ -70,6 +100,38 @@
34F57E87E765BF8D72A43DCA /* Pods_Veloria.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Veloria.framework; sourceTree = BUILT_PRODUCTS_DIR; };
831DCE145EAEF22088BB06E4 /* Pods-Veloria.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Veloria.debug.xcconfig"; path = "Target Support Files/Pods-Veloria/Pods-Veloria.debug.xcconfig"; sourceTree = "<group>"; };
AA827CC8ACC3AE70E33A0EEB /* Pods-Veloria.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Veloria.release.xcconfig"; path = "Target Support Files/Pods-Veloria/Pods-Veloria.release.xcconfig"; sourceTree = "<group>"; };
BF0FA6D42DDC5B5D00C9E5F2 /* VPApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPApi.swift; sourceTree = "<group>"; };
BF0FA6D62DDC5BE100C9E5F2 /* VPURLPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPURLPath.swift; sourceTree = "<group>"; };
BF0FA6D92DDC5CB600C9E5F2 /* VPLoginManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPLoginManager.swift; sourceTree = "<group>"; };
BF0FA6DB2DDC5CD700C9E5F2 /* VPTokenModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPTokenModel.swift; sourceTree = "<group>"; };
BF0FA6DE2DDC5E4D00C9E5F2 /* String+VPAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+VPAdd.swift"; sourceTree = "<group>"; };
BF0FA6E92DDC5F8700C9E5F2 /* JXUUID.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JXUUID.h; sourceTree = "<group>"; };
BF0FA6EA2DDC5F8700C9E5F2 /* JXUUID.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JXUUID.m; sourceTree = "<group>"; };
BF0FA6EB2DDC5F8700C9E5F2 /* PDKeyChain.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PDKeyChain.h; sourceTree = "<group>"; };
BF0FA6EC2DDC5F8700C9E5F2 /* PDKeyChain.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PDKeyChain.m; sourceTree = "<group>"; };
BF0FA6F02DDC600200C9E5F2 /* VPNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNetwork.swift; sourceTree = "<group>"; };
BF0FA6F32DDC604500C9E5F2 /* VPHUD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPHUD.swift; sourceTree = "<group>"; };
BF0FA6F52DDC616300C9E5F2 /* VPToast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPToast.swift; sourceTree = "<group>"; };
BF0FA6F82DDC64E700C9E5F2 /* VPHomeAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPHomeAPI.swift; sourceTree = "<group>"; };
BF0FA6FB2DDC657500C9E5F2 /* VPHomeDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPHomeDataModel.swift; sourceTree = "<group>"; };
BF0FA6FF2DDC665300C9E5F2 /* VPShortModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPShortModel.swift; sourceTree = "<group>"; };
BF0FA7012DDC667C00C9E5F2 /* VPVideoInfoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPVideoInfoModel.swift; sourceTree = "<group>"; };
BF0FA7042DDC67AC00C9E5F2 /* VPHomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPHomeViewModel.swift; sourceTree = "<group>"; };
BF0FA7062DDC687C00C9E5F2 /* VPHomeDataItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPHomeDataItem.swift; sourceTree = "<group>"; };
BF0FA7092DDC69C800C9E5F2 /* VPTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPTableViewCell.swift; sourceTree = "<group>"; };
BF0FA70B2DDC6A3800C9E5F2 /* VPHomeBannerContentCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPHomeBannerContentCell.swift; sourceTree = "<group>"; };
BF0FA70D2DDC6ACC00C9E5F2 /* VPHomeItemContentCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPHomeItemContentCell.swift; sourceTree = "<group>"; };
BF0FA70F2DDC6CA200C9E5F2 /* VPListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPListModel.swift; sourceTree = "<group>"; };
BF0FA7112DDC6D2C00C9E5F2 /* VPHomeModuleItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPHomeModuleItem.swift; sourceTree = "<group>"; };
BF0FA7132DDC78FF00C9E5F2 /* ZKCycleScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZKCycleScrollView.swift; sourceTree = "<group>"; };
BF0FA7142DDC78FF00C9E5F2 /* ZKCycleScrollViewFlowLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZKCycleScrollViewFlowLayout.swift; sourceTree = "<group>"; };
BF0FA7182DDC7F4900C9E5F2 /* VPHomeBannerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPHomeBannerCell.swift; sourceTree = "<group>"; };
BF0FA71A2DDC7FF200C9E5F2 /* VPImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPImageView.swift; sourceTree = "<group>"; };
BF0FA71C2DDC807200C9E5F2 /* UIImageView+VPAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImageView+VPAdd.swift"; sourceTree = "<group>"; };
BF0FA71E2DDC83AE00C9E5F2 /* JXButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JXButton.swift; sourceTree = "<group>"; };
BF0FA7212DDC859D00C9E5F2 /* NSNumber+VPAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSNumber+VPAdd.swift"; sourceTree = "<group>"; };
BF0FA7232DDC888F00C9E5F2 /* VPHomeRecommandContentCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPHomeRecommandContentCell.swift; sourceTree = "<group>"; };
BF0FA7252DDC8F7600C9E5F2 /* VPCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPCollectionView.swift; sourceTree = "<group>"; };
E0BDA3570E00C90877E45AA0 /* Pods-VideoPlayer.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VideoPlayer.debug.xcconfig"; path = "Target Support Files/Pods-VideoPlayer/Pods-VideoPlayer.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -110,7 +172,7 @@
1B056E372DDAC1EF007EE38D /* Base */,
1B056E362DDAC1E8007EE38D /* Class */,
1B056E352DDAC1DE007EE38D /* Libs */,
1B056E342DDAC1C7007EE38D /* Thirdparty */,
BF0FA6E82DDC5F6F00C9E5F2 /* Thirdparty */,
1B056E332DDAC1AB007EE38D /* Source */,
);
path = Veloria;
@ -137,16 +199,11 @@
path = Source;
sourceTree = "<group>";
};
1B056E342DDAC1C7007EE38D /* Thirdparty */ = {
isa = PBXGroup;
children = (
);
path = Thirdparty;
sourceTree = "<group>";
};
1B056E352DDAC1DE007EE38D /* Libs */ = {
isa = PBXGroup;
children = (
BF0FA6F22DDC603200C9E5F2 /* HUD */,
BF0FA6D82DDC5C8200C9E5F2 /* Login */,
1B056E652DDAD098007EE38D /* LocalizedManager */,
1B056E472DDAC3CB007EE38D /* AppTool */,
);
@ -157,6 +214,7 @@
isa = PBXGroup;
children = (
1B056E522DDACBF4007EE38D /* Home */,
BF0FA6FD2DDC65F300C9E5F2 /* Player */,
);
path = Class;
sourceTree = "<group>";
@ -189,6 +247,9 @@
children = (
1B056E752DDB35C5007EE38D /* TabBar */,
1B056E7A2DDB37BA007EE38D /* VPGradientView.swift */,
BF0FA7092DDC69C800C9E5F2 /* VPTableViewCell.swift */,
BF0FA71A2DDC7FF200C9E5F2 /* VPImageView.swift */,
BF0FA7252DDC8F7600C9E5F2 /* VPCollectionView.swift */,
);
path = View;
sourceTree = "<group>";
@ -197,6 +258,7 @@
isa = PBXGroup;
children = (
1B056E402DDAC30A007EE38D /* VPModel.swift */,
BF0FA70F2DDC6CA200C9E5F2 /* VPListModel.swift */,
);
path = Model;
sourceTree = "<group>";
@ -204,6 +266,7 @@
1B056E3B2DDAC22D007EE38D /* Networking */ = {
isa = PBXGroup;
children = (
BF0FA6F72DDC64CF00C9E5F2 /* API */,
1B056E3D2DDAC283007EE38D /* Base */,
);
path = Networking;
@ -221,6 +284,9 @@
isa = PBXGroup;
children = (
1B056E3E2DDAC2DB007EE38D /* VPCryptorService.swift */,
BF0FA6D42DDC5B5D00C9E5F2 /* VPApi.swift */,
BF0FA6D62DDC5BE100C9E5F2 /* VPURLPath.swift */,
BF0FA6F02DDC600200C9E5F2 /* VPNetwork.swift */,
);
path = Base;
sourceTree = "<group>";
@ -233,6 +299,9 @@
1B056E5A2DDACD80007EE38D /* UIColor+VPAdd.swift */,
1B056E5C2DDACD8E007EE38D /* UIFont+VPAdd.swift */,
1B056E732DDB2DD7007EE38D /* UIView+VPAdd.swift */,
BF0FA6DE2DDC5E4D00C9E5F2 /* String+VPAdd.swift */,
BF0FA71C2DDC807200C9E5F2 /* UIImageView+VPAdd.swift */,
BF0FA7212DDC859D00C9E5F2 /* NSNumber+VPAdd.swift */,
);
path = Extension;
sourceTree = "<group>";
@ -249,26 +318,13 @@
isa = PBXGroup;
children = (
1B056E552DDACC08007EE38D /* Controller */,
1B056E542DDACC03007EE38D /* View */,
1B056E532DDACBFA007EE38D /* Model */,
BF0FA7082DDC699A00C9E5F2 /* View */,
BF0FA6FA2DDC652000C9E5F2 /* Model */,
BF0FA7032DDC679B00C9E5F2 /* ViewModel */,
);
path = Home;
sourceTree = "<group>";
};
1B056E532DDACBFA007EE38D /* Model */ = {
isa = PBXGroup;
children = (
);
path = Model;
sourceTree = "<group>";
};
1B056E542DDACC03007EE38D /* View */ = {
isa = PBXGroup;
children = (
);
path = View;
sourceTree = "<group>";
};
1B056E552DDACC08007EE38D /* Controller */ = {
isa = PBXGroup;
children = (
@ -317,6 +373,116 @@
name = Frameworks;
sourceTree = "<group>";
};
BF0FA6D82DDC5C8200C9E5F2 /* Login */ = {
isa = PBXGroup;
children = (
BF0FA6D92DDC5CB600C9E5F2 /* VPLoginManager.swift */,
BF0FA6DB2DDC5CD700C9E5F2 /* VPTokenModel.swift */,
);
path = Login;
sourceTree = "<group>";
};
BF0FA6E82DDC5F6F00C9E5F2 /* Thirdparty */ = {
isa = PBXGroup;
children = (
BF0FA6ED2DDC5F8700C9E5F2 /* JXUUID */,
BF0FA7152DDC78FF00C9E5F2 /* ZKCycleScrollView-Swift */,
BF0FA71F2DDC83AE00C9E5F2 /* JXButton */,
);
path = Thirdparty;
sourceTree = "<group>";
};
BF0FA6ED2DDC5F8700C9E5F2 /* JXUUID */ = {
isa = PBXGroup;
children = (
BF0FA6E92DDC5F8700C9E5F2 /* JXUUID.h */,
BF0FA6EA2DDC5F8700C9E5F2 /* JXUUID.m */,
BF0FA6EB2DDC5F8700C9E5F2 /* PDKeyChain.h */,
BF0FA6EC2DDC5F8700C9E5F2 /* PDKeyChain.m */,
);
path = JXUUID;
sourceTree = "<group>";
};
BF0FA6F22DDC603200C9E5F2 /* HUD */ = {
isa = PBXGroup;
children = (
BF0FA6F32DDC604500C9E5F2 /* VPHUD.swift */,
BF0FA6F52DDC616300C9E5F2 /* VPToast.swift */,
);
path = HUD;
sourceTree = "<group>";
};
BF0FA6F72DDC64CF00C9E5F2 /* API */ = {
isa = PBXGroup;
children = (
BF0FA6F82DDC64E700C9E5F2 /* VPHomeAPI.swift */,
);
path = API;
sourceTree = "<group>";
};
BF0FA6FA2DDC652000C9E5F2 /* Model */ = {
isa = PBXGroup;
children = (
BF0FA6FB2DDC657500C9E5F2 /* VPHomeDataModel.swift */,
BF0FA7062DDC687C00C9E5F2 /* VPHomeDataItem.swift */,
BF0FA7112DDC6D2C00C9E5F2 /* VPHomeModuleItem.swift */,
);
path = Model;
sourceTree = "<group>";
};
BF0FA6FD2DDC65F300C9E5F2 /* Player */ = {
isa = PBXGroup;
children = (
BF0FA6FE2DDC660300C9E5F2 /* Model */,
);
path = Player;
sourceTree = "<group>";
};
BF0FA6FE2DDC660300C9E5F2 /* Model */ = {
isa = PBXGroup;
children = (
BF0FA6FF2DDC665300C9E5F2 /* VPShortModel.swift */,
BF0FA7012DDC667C00C9E5F2 /* VPVideoInfoModel.swift */,
);
path = Model;
sourceTree = "<group>";
};
BF0FA7032DDC679B00C9E5F2 /* ViewModel */ = {
isa = PBXGroup;
children = (
BF0FA7042DDC67AC00C9E5F2 /* VPHomeViewModel.swift */,
);
path = ViewModel;
sourceTree = "<group>";
};
BF0FA7082DDC699A00C9E5F2 /* View */ = {
isa = PBXGroup;
children = (
BF0FA70D2DDC6ACC00C9E5F2 /* VPHomeItemContentCell.swift */,
BF0FA70B2DDC6A3800C9E5F2 /* VPHomeBannerContentCell.swift */,
BF0FA7232DDC888F00C9E5F2 /* VPHomeRecommandContentCell.swift */,
BF0FA7182DDC7F4900C9E5F2 /* VPHomeBannerCell.swift */,
);
path = View;
sourceTree = "<group>";
};
BF0FA7152DDC78FF00C9E5F2 /* ZKCycleScrollView-Swift */ = {
isa = PBXGroup;
children = (
BF0FA7132DDC78FF00C9E5F2 /* ZKCycleScrollView.swift */,
BF0FA7142DDC78FF00C9E5F2 /* ZKCycleScrollViewFlowLayout.swift */,
);
path = "ZKCycleScrollView-Swift";
sourceTree = "<group>";
};
BF0FA71F2DDC83AE00C9E5F2 /* JXButton */ = {
isa = PBXGroup;
children = (
BF0FA71E2DDC83AE00C9E5F2 /* JXButton.swift */,
);
path = JXButton;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -436,8 +602,14 @@
buildActionMask = 2147483647;
files = (
1B056E742DDB2DD7007EE38D /* UIView+VPAdd.swift in Sources */,
BF0FA6F12DDC600200C9E5F2 /* VPNetwork.swift in Sources */,
1B056E572DDACC6B007EE38D /* VPHomePageViewController.swift in Sources */,
BF0FA6DF2DDC5E4D00C9E5F2 /* String+VPAdd.swift in Sources */,
BF0FA7002DDC665300C9E5F2 /* VPShortModel.swift in Sources */,
1B056E7B2DDB37BA007EE38D /* VPGradientView.swift in Sources */,
BF0FA7262DDC8F7600C9E5F2 /* VPCollectionView.swift in Sources */,
BF0FA71D2DDC807200C9E5F2 /* UIImageView+VPAdd.swift in Sources */,
BF0FA6DC2DDC5CD700C9E5F2 /* VPTokenModel.swift in Sources */,
1B056E772DDB3641007EE38D /* VPTabBarItemNormalVew.swift in Sources */,
1B056E4B2DDAC6BA007EE38D /* VPDefine.swift in Sources */,
1B056E702DDB019B007EE38D /* VPTabBarItemContainer.swift in Sources */,
@ -445,19 +617,43 @@
1B056E2B2DDAC0FD007EE38D /* AppDelegate.swift in Sources */,
1B056E6C2DDADAA1007EE38D /* VPTabBar.swift in Sources */,
1B056E5D2DDACD8E007EE38D /* UIFont+VPAdd.swift in Sources */,
BF0FA6F92DDC64E700C9E5F2 /* VPHomeAPI.swift in Sources */,
BF0FA6F42DDC604500C9E5F2 /* VPHUD.swift in Sources */,
BF0FA7222DDC859D00C9E5F2 /* NSNumber+VPAdd.swift in Sources */,
BF0FA71B2DDC7FF200C9E5F2 /* VPImageView.swift in Sources */,
1B056E2C2DDAC0FD007EE38D /* SceneDelegate.swift in Sources */,
1B056E462DDAC370007EE38D /* UIScreen+VPAdd.swift in Sources */,
BF0FA6EE2DDC5F8700C9E5F2 /* JXUUID.m in Sources */,
BF0FA7052DDC67AC00C9E5F2 /* VPHomeViewModel.swift in Sources */,
BF0FA6EF2DDC5F8700C9E5F2 /* PDKeyChain.m in Sources */,
1B056E4D2DDAC7C1007EE38D /* VPTabBarController.swift in Sources */,
BF0FA6DA2DDC5CB600C9E5F2 /* VPLoginManager.swift in Sources */,
1B056E4F2DDAC7FC007EE38D /* VPNavigationController.swift in Sources */,
1B056E3F2DDAC2DB007EE38D /* VPCryptorService.swift in Sources */,
BF0FA7202DDC83AE00C9E5F2 /* JXButton.swift in Sources */,
BF0FA7072DDC687C00C9E5F2 /* VPHomeDataItem.swift in Sources */,
BF0FA70E2DDC6ACC00C9E5F2 /* VPHomeItemContentCell.swift in Sources */,
BF0FA6D72DDC5BE100C9E5F2 /* VPURLPath.swift in Sources */,
1B056E5B2DDACD80007EE38D /* UIColor+VPAdd.swift in Sources */,
1B056E6A2DDAD0BF007EE38D /* VPLocalizedManager.swift in Sources */,
BF0FA7242DDC888F00C9E5F2 /* VPHomeRecommandContentCell.swift in Sources */,
BF0FA7192DDC7F4900C9E5F2 /* VPHomeBannerCell.swift in Sources */,
BF0FA7022DDC667C00C9E5F2 /* VPVideoInfoModel.swift in Sources */,
1B056E792DDB365A007EE38D /* VPTabBarItemSelectedView.swift in Sources */,
1B056E722DDB022F007EE38D /* VPTabBarItem.swift in Sources */,
1B056E412DDAC30A007EE38D /* VPModel.swift in Sources */,
BF0FA70A2DDC69C800C9E5F2 /* VPTableViewCell.swift in Sources */,
1B056E442DDAC355007EE38D /* UIDevice+VPAdd.swift in Sources */,
1B056E512DDACBE5007EE38D /* VPViewController.swift in Sources */,
BF0FA7122DDC6D2C00C9E5F2 /* VPHomeModuleItem.swift in Sources */,
BF0FA7162DDC78FF00C9E5F2 /* ZKCycleScrollViewFlowLayout.swift in Sources */,
BF0FA7172DDC78FF00C9E5F2 /* ZKCycleScrollView.swift in Sources */,
BF0FA6D52DDC5B5D00C9E5F2 /* VPApi.swift in Sources */,
BF0FA6FC2DDC657500C9E5F2 /* VPHomeDataModel.swift in Sources */,
BF0FA6F62DDC616300C9E5F2 /* VPToast.swift in Sources */,
1B056E492DDAC3DF007EE38D /* VPAppTool.swift in Sources */,
BF0FA70C2DDC6A3800C9E5F2 /* VPHomeBannerContentCell.swift in Sources */,
BF0FA7102DDC6CA200C9E5F2 /* VPListModel.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -51,9 +51,11 @@ class VPTabBarController: UITabBarController {
tabBar.delegate = self
self.setValue(tabBar, forKey: "tabBar")
view.backgroundColor = .color000000()
// ESTabBarController
vp_setup()
// self.tabBar.isTranslucent = false
self.tabBar.isTranslucent = true
}
@ -75,9 +77,10 @@ extension VPTabBarController {
let appearance = UITabBarAppearance()
appearance.backgroundImage = UIImage(color: .clear)
appearance.backgroundColor = .red
appearance.backgroundColor = .colorFFFFFF(alpha: 0.1)
appearance.shadowImage = UIImage()
appearance.shadowColor = .clear
appearance.backgroundEffect = UIBlurEffect(style: .dark)
tabBar.standardAppearance = appearance
tabBar.scrollEdgeAppearance = appearance

View File

@ -8,12 +8,23 @@
import UIKit
class VPViewController: UIViewController {
private(set) lazy var bgImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "bg_image_01"))
return imageView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .backgroundColor()
view.addSubview(bgImageView)
bgImageView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}

View File

@ -8,6 +8,18 @@
import UIKit
//MARK:-------------- --------------
///
let kVPOsVersion: String = UIDevice.current.systemVersion
let kVPAPPBundleIdentifier: String = (Bundle.main.infoDictionary!["CFBundleIdentifier"] as? String) ?? "0"
///app
public let kVPAPPVersion: String = (Bundle.main.infoDictionary!["CFBundleShortVersionString"] as? String) ?? "0"
public let kVPAPPBundleVersion: String = (Bundle.main.infoDictionary!["CFBundleVersion"] as? String) ?? "0"
public let kVPAPPBundleName: String = (Bundle.main.infoDictionary!["CFBundleName"] as? String) ?? ""
public let kVPAPPName: String = (Bundle.main.infoDictionary!["CFBundleDisplayName"] as? String) ?? ""
//MARK: ------- ----------
#if DEBUG

View File

@ -0,0 +1,25 @@
//
// NSNumber+VPAdd.swift
// Veloria
//
// Created by Veloria on 2025/5/20.
//
import UIKit
extension NSNumber {
func toString(maximumFractionDigits: Int = 10, minimumFractionDigits: Int? = nil, roundingMode: NumberFormatter.RoundingMode? = nil) -> String {
let formatter = NumberFormatter()
formatter.minimumIntegerDigits = 1
formatter.maximumFractionDigits = maximumFractionDigits
if let minimumFractionDigits = minimumFractionDigits {
formatter.minimumFractionDigits = minimumFractionDigits
}
if let roundingMode = roundingMode {
formatter.roundingMode = roundingMode
}
formatter.numberStyle = .none
return formatter.string(from: self) ?? "0"
}
}

View File

@ -0,0 +1,33 @@
//
// String+VPAdd.swift
// Veloria
//
// Created by Veloria on 2025/5/20.
//
import UIKit
import SmartCodable
extension String: SmartCodable {
func length() -> Int {
return self.ocString().length
}
func ocString() -> NSString {
return self as NSString
}
static func timeZone() -> String {
let timeZone = NSTimeZone.local as NSTimeZone
let timeZoneSecondsFromGMT = timeZone.secondsFromGMT / 3600
return String(format: "GMT+0%d:00", timeZoneSecondsFromGMT)
}
}
extension String {
///Size
func size(font: UIFont, size: CGSize = CGSize(width: CGFloat(MAXFLOAT), height: CGFloat(MAXFLOAT))) -> CGSize{
let string: NSString = self as NSString
return string.size(for: font, size: size, mode: .byWordWrapping)
}
}

View File

@ -33,4 +33,12 @@ extension UIColor {
static func color353537(alpha: CGFloat = 1) -> UIColor {
return UIColor(rgb: 0x353537, alpha: alpha)
}
static func color828282(alpha: CGFloat = 1) -> UIColor {
return UIColor(rgb: 0x828282, alpha: alpha)
}
static func colorAFAFAF(alpha: CGFloat = 1) -> UIColor {
return UIColor(rgb: 0xAFAFAF, alpha: alpha)
}
}

View File

@ -8,3 +8,67 @@
import UIKit
extension UIDevice {
//http://theiphonewiki.com/wiki/Models
static func sp_machineModelName() -> String {
guard let machineModel = UIDevice.current.machineModel else { return "" }
let map = [
"iPhone1,1" : "iPhone",
"iPhone1,2" : "iPhone 3G",
"iPhone2,1" : "iPhone 3GS",
"iPhone3,1" : "iPhone 4",
"iPhone3,2" : "iPhone 4",
"iPhone3,3" : "iPhone 4",
"iPhone4,1" : "iPhone4,1",
"iPhone5,1" : "iPhone 5",
"iPhone5,2" : "iPhone 5",
"iPhone5,3" : "iPhone 5c",
"iPhone5,4" : "iPhone 5c",
"iPhone6,1" : "iPhone 5s",
"iPhone6,2" : "iPhone 5s",
"iPhone7,2" : "iPhone 6",
"iPhone7,1" : "iPhone 6 Plus",
"iPhone8,1" : "iPhone 6s",
"iPhone8,2" : "iPhone 6s Plus",
"iPhone8,4" : "iPhone SE (1st generation)",
"iPhone9,1" : "iPhone 7",
"iPhone9,3" : "iPhone 7",
"iPhone9,2" : "iPhone 7 Plus",
"iPhone9,4" : "iPhone 7 Plus",
"iPhone10,1" : "iPhone 8",
"iPhone10,4" : "iPhone 8",
"iPhone10,2" : "iPhone 8 Plus",
"iPhone10,5" : "iPhone 8 Plus",
"iPhone10,3" : "iPhone X",
"iPhone10,6" : "iPhone X",
"iPhone11,8" : "iPhone XR",
"iPhone11,2" : "iPhone11,2",
"iPhone11,6" : "iPhone XS Max",
"iPhone11,4" : "iPhone XS Max",
"iPhone12,1" : "iPhone 11",
"iPhone12,3" : "iPhone 11 Pro",
"iPhone12,5" : "iPhone 11 Pro Max",
"iPhone12,8" : "iPhone SE (2nd generation)",
"iPhone13,1" : "iPhone 12 mini",
"iPhone13,2" : "iPhone13,2",
"iPhone13,3" : "iPhone 12 Pro",
"iPhone13,4" : "iPhone 12 Pro Max",
"iPhone14,4" : "iPhone 13 mini",
"iPhone14,5" : "iPhone 13",
"iPhone14,2" : "iPhone 13 Pro",
"iPhone14,3" : "iPhone 13 Pro Max",
"iPhone14,6" : "iPhone SE (3rd generation)",
"iPhone14,7" : "iPhone 14",
"iPhone14,8" : "iPhone 14 Plus",
"iPhone15,2" : "iPhone 14 Pro",
"iPhone15,3" : "iPhone 14 Pro Max",
]
if let name = map[machineModel] {
return name
}
return machineModel
}
}

View File

@ -0,0 +1,24 @@
//
// UIImageView+VPAdd.swift
// Veloria
//
// Created by Veloria on 2025/5/20.
//
import UIKit
import Kingfisher
extension UIImageView {
func vp_setImage(url: String?, placeholder: UIImage? = nil, completer: ((_ image: UIImage?, _ url: URL?) -> Void)? = nil) {
self.kf.setImage(with: URL(string: url ?? ""), placeholder: placeholder, options: nil) { result in
switch result {
case .success(let value):
completer?(value.image, value.source.url)
default :
completer?(nil, nil)
break
}
}
}
}

View File

@ -0,0 +1,22 @@
//
// VPListModel.swift
// Veloria
//
// Created by Veloria on 2025/5/20.
//
import UIKit
import SmartCodable
class VPListModel<T: SmartCodable>: VPModel, SmartCodable {
var list: [T]?
var pagination: VPListPaginationModel?
}
class VPListPaginationModel: VPModel, SmartCodable {
var current_page: Int?
var page_size: Int?
var page_total: Int?
var total_size: Int?
}

View File

@ -9,7 +9,7 @@ import UIKit
class VPModel: NSObject {
override init() {
required override init() {
super.init()
}

View File

@ -0,0 +1,22 @@
//
// VPHomeAPI.swift
// Veloria
//
// Created by Veloria on 2025/5/20.
//
import UIKit
class VPHomeAPI: NSObject {
///
static func requestHomeData(completer: ((_ list: [VPHomeModuleItem]?) -> Void)?) {
var param = VPNetworkParameters(path: "/home/all-modules")
param.method = .get
VPNetwork.request(parameters: param) { (response: VPNetworkResponse<VPListModel<VPHomeModuleItem>>) in
completer?(response.data?.list)
}
}
}

View File

@ -0,0 +1,116 @@
//
// VPApi.swift
// Veloria
//
// Created by Veloria on 2025/5/20.
//
import UIKit
import Moya
import SmartCodable
struct VPNetworkData<T: SmartCodable> {
var parameters: VPNetworkParameters?
var completion: ((_ response: VPNetworkResponse<T>) -> Void)?
}
struct VPNetworkParameters {
var baseURL: URL?
var parameters: [String : Any]?
var method: Moya.Method = .post
var path: String
var isLoding: Bool = false
var isToast: Bool = true
}
struct VPNetworkResponse<T: SmartCodable>: SmartCodable {
var code: Int?
var data: T?
var msg: String?
///
@IgnoredKey
var rawData: Any?
}
enum VPApi {
case request(parameters: VPNetworkParameters)
case uploadFile(parameters: VPNetworkParameters)
case downloadFile(parameters: VPNetworkParameters)
}
extension VPApi: TargetType {
var baseURL: URL {
return URL(string: VPBaseURL)!
}
var path: String {
switch self {
case .request(let parameters):
return VPURLPathPrefix + parameters.path
default:
return ""
}
}
var method: Moya.Method {
switch self {
case .request(let parameters):
return parameters.method
default:
return .post
}
}
var task: Moya.Task {
switch self {
case .request(let parameters):
let p = parameters.parameters ?? [:]
return .requestParameters(parameters: p, encoding: getEncoding())
default:
return .requestParameters(parameters: [:], encoding: getEncoding())
}
}
var headers: [String : String]? {
let userToken = VPLoginManager.manager.token?.token ?? ""
var dic: [String : String] = [
"system-version" : kVPOsVersion,
"lang-key" : VPLocalizedManager.shared.currentLocalizedKey,//
"time-zone" : String.timeZone(), //
"app-version" : kVPAPPVersion,
// "device-id" : JXUUID.systemUUID(), //id
"device-id" : JXUUID.uuid(), //id
"brand" : "apple", //
"app-name" : "",
"system-type" : "ios",
"idfa" : JXUUID.idfa(),
"model" : UIDevice.sp_machineModelName(),
// "security" : "false",
]
//
dic["authorization"] = userToken
return dic
}
}
extension TargetType {
var sampleData: Data { return "".data(using: String.Encoding.utf8)! }
func getEncoding() -> ParameterEncoding {
switch self.method {
case .get, .delete:
return URLEncoding.default
default:
return JSONEncoding.default
}
}
}

View File

@ -0,0 +1,224 @@
//
// VPNetwork.swift
// Veloria
//
// Created by Veloria on 2025/5/20.
//
import UIKit
import Moya
import SmartCodable
///
let VPNetworkCodeSucceed = 200
class VPNetwork: NSObject {
///token线
private static let operationQueue = OperationQueue()
private static var tokenOperation: BlockOperation?
static let provider = MoyaProvider<VPApi>(requestClosure: CustomApiTimeoutClosure)
static func request<T>(parameters: VPNetworkParameters, completion: ((_ response: VPNetworkResponse<T>) -> Void)?) {
if VPLoginManager.manager.token == nil {
self.requestToken(completer: nil)
}
if let tokenOperation = self.tokenOperation, parameters.path != "/customer/register" {
let requestOperation = BlockOperation {
let semaphore = DispatchSemaphore(value: 0)
_request(parameters: parameters) { (response: VPNetworkResponse<T>) in
semaphore.signal()
completion?(response)
}
semaphore.wait()
}
///
requestOperation.addDependency(tokenOperation)
operationQueue.addOperation(requestOperation)
} else {
_request(parameters: parameters, completion: completion)
}
}
@discardableResult
static func _request<T>(parameters: VPNetworkParameters, completion: ((_ response: VPNetworkResponse<T>) -> Void)?) -> Cancellable {
if parameters.isLoding {
VPHUD.show()
}
return provider.requestCustomJson(.request(parameters: parameters)) { (result) in
if parameters.isLoding {
VPHUD.dismiss()
}
guard let completion = completion else {return}
_resultDispose(parameters: parameters, result: result, completion: completion)
}
}
private static func _resultDispose<T>(parameters: VPNetworkParameters, result: Result<Moya.Response, MoyaError>, completion: ((_ response: VPNetworkResponse<T>) -> Void)?) {
switch result {
case .success(let response):
let code = response.statusCode
if code == 401 || code == 402 || code == 403 {
if parameters.path == "/customer/register" {
var res = VPNetworkResponse<T>()
res.code = -1
if parameters.isToast {
VPToast.show(text: "movia_error".localized)
}
completion?(res)
} else {
///token
self.requestToken(completer: nil)
///
if let tokenOperation = self.tokenOperation, parameters.path != "/customer/register" {
let requestOperation = BlockOperation {
let semaphore = DispatchSemaphore(value: 0)
_request(parameters: parameters) { (response: VPNetworkResponse<T>) in
semaphore.signal()
completion?(response)
}
semaphore.wait()
}
///
requestOperation.addDependency(tokenOperation)
operationQueue.addOperation(requestOperation)
}
}
return
}
do {
let tempData = try response.mapString()
vpLog(message: parameters.parameters)
vpLog(message: parameters.path)
DispatchQueue.global().async {
let response: VPNetworkResponse<T> = _deserialize(data: tempData)
DispatchQueue.main.async {
if response.code != VPNetworkCodeSucceed {
if parameters.isToast {
VPToast.show(text: response.msg)
}
}
completion?(response)
}
}
} catch {
var res = VPNetworkResponse<T>()
res.code = -1
if parameters.isToast {
VPToast.show(text: "movia_error".localized)
}
completion?(res)
}
case .failure(let error):
vpLog(message: error)
var res = VPNetworkResponse<T>()
res.code = -1
if parameters.isToast {
VPToast.show(text: "movia_network_toast_01".localized)
}
completion?(res)
break
}
}
///
static private func _deserialize<T>(data: String) -> VPNetworkResponse<T> {
var response: VPNetworkResponse<T>?
let time = Date().timeIntervalSince1970
let decrypted = VPCryptorService.decrypt(data: data)
vpLog(message: decrypted)
response = VPNetworkResponse<T>.deserialize(from: decrypted)
response?.rawData = decrypted
vpLog(message: Date().timeIntervalSince1970 - time)
if let response = response {
return response
} else {
var response = VPNetworkResponse<T>()
response.code = -1
response.msg = "movia_error".localized
return response
}
}
}
extension VPNetwork {
///token
static func requestToken(completer: ((_ token: VPTokenModel?) -> Void)?) {
guard self.tokenOperation == nil else {
completer?(nil)
return
}
self.tokenOperation = BlockOperation(block: {
let semaphore = DispatchSemaphore(value: 0)
let param = VPNetworkParameters(path: "/customer/register")
VPNetwork.request(parameters: param) { (response: VPNetworkResponse<VPTokenModel>) in
if let token = response.data {
VPLoginManager.manager.setLoginToken(token: token)
}
do { semaphore.signal() }
self.tokenOperation = nil
completer?(response.data)
}
semaphore.wait()
})
operationQueue.addOperation(self.tokenOperation!)
}
}
extension MoyaProvider {
@discardableResult
func requestCustomJson(_ target: Target, callbackQueue: DispatchQueue? = nil, completion: Completion?) -> Cancellable {
return request(target, callbackQueue: callbackQueue) { (result) in
guard let completion = completion else {return}
completion(result)
}
}
}
let CustomApiTimeoutClosure = {(endpoint: Endpoint, closure: MoyaProvider<VPApi>.RequestResultClosure) -> Void in
if var urlRequest = try? endpoint.urlRequest() {
///
urlRequest.cachePolicy = .reloadIgnoringCacheData
urlRequest.timeoutInterval = 30
closure(.success(urlRequest))
} else {
closure(.failure(MoyaError.requestMapping(endpoint.url)))
}
#if DEBUG ///
//print(try? endpoint.urlRequest() )
#endif
}

View File

@ -0,0 +1,46 @@
//
// VPURLPath.swift
// Veloria
//
// Created by Veloria on 2025/5/20.
//
import UIKit
#if DEBUG
let VPBaseURL = "https://api-qjwl168.qjwl168.com"
let VPURLPathPrefix = "/velo"
let VPWebBaseURL = "https://www.thimratv.com"
let VPCampaignWebURL = "https://campaign.moviatv.com"
#else
let VPBaseURL = "https://api-qjwl168.qjwl168.com"
let VPURLPathPrefix = "/velo"
let VPWebBaseURL = "https://www.thimratv.com"
let VPCampaignWebURL = "https://campaign.moviatv.com"
#endif
///
let VPUserAgreementWebUrl = VPWebBaseURL + "/user_policy"
///
let VPPrivacyPolicyWebUrl = VPWebBaseURL + "/private"
///
let VPInformationProtectionWebUrl = VPWebBaseURL + "/information_protection"
///
let VPInformationSharingWebUrl = VPWebBaseURL + "/information_sharing"
///
let VPPersoInforDisclosureWebUrl = VPWebBaseURL + "/persoInfor_disclosure"
///
let VPCivizatioConventionWebUrl = VPWebBaseURL + "/civizatio_convention"
///
let VPMemberShipAgreement = VPWebBaseURL + "/member_ship_agreement"
///
let VPFeedBackHomeWebUrl = VPCampaignWebURL + "/pages/leave/index"
///
let VPFeedBackListWebUrl = VPCampaignWebURL + "/pages/leave/list"
///
let VPFeedBackDetailWebUrl = VPCampaignWebURL + "/pages/leave/detail"
///
let VPRewardsWebUrl = VPCampaignWebURL

View File

@ -0,0 +1,22 @@
//
// VPCollectionView.swift
// Veloria
//
// Created by Veloria on 2025/5/20.
//
import UIKit
class VPCollectionView: UICollectionView {
override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
super.init(frame: frame, collectionViewLayout: layout)
self.backgroundColor = .clear
self.contentInsetAdjustmentBehavior = .never
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View File

@ -0,0 +1,54 @@
//
// VPImageView.swift
// Veloria
//
// Created by Veloria on 2025/5/20.
//
import UIKit
class VPImageView: UIImageView {
var placeholderColor = UIColor.gray
override init(frame: CGRect) {
super.init(frame: frame)
_init()
}
override init(image: UIImage?) {
super.init(image: image)
_init()
}
override init(image: UIImage?, highlightedImage: UIImage?) {
super.init(image: image, highlightedImage: highlightedImage)
_init()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func _init() {
self.contentMode = .scaleAspectFill
self.layer.masksToBounds = true
if image == nil {
self.backgroundColor = self.placeholderColor
}
}
override var image: UIImage? {
didSet {
if self.backgroundColor == nil && image == nil {
self.backgroundColor = self.placeholderColor
} else if image != nil {
if self.backgroundColor == self.placeholderColor {
self.backgroundColor = nil
}
}
}
}
}

View File

@ -0,0 +1,43 @@
//
// VPTableViewCell.swift
// Veloria
//
// Created by Veloria on 2025/5/20.
//
import UIKit
class VPTableViewCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.layer.rasterizationScale = UIScreen.main.scale
self.layer.shouldRasterize = true
self.selectionStyle = .none
self.backgroundColor = .clear
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
extension UITableViewCell {
var vp_tableView: UITableView? {
return self.value(forKey: "_tableView") as? UITableView
}
}

View File

@ -7,16 +7,132 @@
import UIKit
class VPHomePageViewController: UIViewController {
class VPHomePageViewController: VPViewController {
private lazy var viewModel = VPHomeViewModel()
private lazy var pageParam: WMZPageParam = {
let param = WMZPageParam()
param.wTitleArr = ["All".localized, "Romance", "Revenge"]
param.wViewController = { [weak self] index in
return VPViewController()
}
param.wTopSuspension = true
//
param.wBounces = true
param.wMenuTitleColor = .color828282()
param.wMenuTitleSelectColor = .colorFFFFFF()
param.wMenuTitleUIFont = .fontRegular(ofSize: 12)
param.wMenuTitleSelectUIFont = .fontMedium(ofSize: 12)
param.wMenuBgColor = .clear
param.wBgColor = .clear
param.wCustomMenuTitle = { [weak self] buttons in
vpLog(message: "++++++++++\(buttons ?? [])")
}
param.wCustomMenuSelectTitle = { buttons in
vpLog(message: "----------\(buttons ?? [])")
}
param.wCustomNaviBarY = { _ in
return 0
}
param.wCustomTabbarY = { _ in
return 0
}
return param
}()
private lazy var pageView: WMZPageView = {
let y = UIScreen.statusBarHeight
let width = UIScreen.width
let height = UIScreen.height - y
let pageView = WMZPageView(frame: CGRect(x: 0, y: y, width: width, height: height), param: pageParam, parentReponder: self)
pageView.param = pageParam
pageView.backgroundColor = .clear
pageView.downSc?.backgroundColor = .clear
// pageView.downSc?.delegate = self
pageView.downSc?.dataSource = self
pageView.downSc?.isScrollEnabled = true
pageView.downSc?.register(VPHomeItemContentCell.self, forCellReuseIdentifier: "cell")
pageView.downSc?.register(VPHomeBannerContentCell.self, forCellReuseIdentifier: VPHomeBannerContentCell.moduleKey.rawValue)
pageView.downSc?.register(VPHomeRecommandContentCell.self, forCellReuseIdentifier: VPHomeRecommandContentCell.moduleKey.rawValue)
// pageView.downSc?.rowHeight = UITableView.automaticDimension
// pageView.downSc?.estimatedRowHeight = 100
return pageView
}()
override func viewDidLoad() {
super.viewDidLoad()
requestHomeData()
vp_setupUI()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(true, animated: true)
}
private func createView(title: String) -> WMZPageNaviBtn {
let button = WMZPageNaviBtn(type: .custom)
button.setTitle(title, for: .normal)
button.setTitleColor(.red, for: .normal)
button.setTitleColor(.green, for: .selected)
return button
}
}
extension VPHomePageViewController {
private func vp_setupUI() {
view.addSubview(pageView)
}
}
//MARK: -------------- UITableViewDataSource --------------
extension VPHomePageViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = self.viewModel.newModuleList[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: item.module_key?.rawValue ?? "cell") as! VPHomeItemContentCell
cell.item = item
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.viewModel.newModuleList.count
}
}
extension VPHomePageViewController {
private func requestHomeData() {
VPHomeAPI.requestHomeData { [weak self] list in
guard let self = self else { return }
if let list = list {
self.viewModel.oldModuleList = list
self.pageView.downSc?.reloadData()
}
}
}
}

View File

@ -0,0 +1,20 @@
//
// VPHomeDataItem.swift
// Veloria
//
// Created by Veloria on 2025/5/20.
//
import UIKit
class VPHomeDataItem: VPModel {
enum ItemType: String {
case banner
}
var type: ItemType = .banner
var list: [VPShortModel]?
}

View File

@ -0,0 +1,27 @@
//
// VPHomeDataModel.swift
// Veloria
//
// Created by Veloria on 2025/5/20.
//
import UIKit
import SmartCodable
class VPHomeDataModel: VPModel, SmartCodable {
var bannerData: [VPShortModel]?
///
var nineSquare: VPNineSquareModel?
///
var manualNewestRecommand: [VPShortModel]?
///
var hotData: [VPShortModel]?
}
class VPNineSquareModel: VPModel, SmartCodable {
var list: [VPShortModel]?
var title: String?
}

View File

@ -0,0 +1,38 @@
//
// VPHomeModuleItem.swift
// Veloria
//
// Created by Veloria on 2025/5/20.
//
import UIKit
import SmartCodable
class VPHomeModuleItem: VPModel, SmartCodable {
enum ModuleKey: String, SmartCaseDefaultable {
case banner = "home_banner"
case v3_recommand = "home_v3_recommand"
}
var module_key: ModuleKey?
var title: String?
var list: [VPShortModel]?
@SmartAny
var data: Any?
func didFinishMapping() {
if let data = data as? [[String : Any]] {
self.list = [VPShortModel].deserialize(from: data)
} else if let data = data as? [String : Any] {
title = data["title"] as? String
list = [VPShortModel].deserialize(from: data["list"] as? [[String : Any]])
}
}
}

View File

@ -0,0 +1,98 @@
//
// VPHomeBannerCell.swift
// Veloria
//
// Created by Veloria on 2025/5/20.
//
import UIKit
class VPHomeBannerCell: ZKCycleScrollViewCell {
var model: VPShortModel? {
didSet {
coverImageView.vp_setImage(url: model?.horizontally_img)
titleLabel.text = model?.name
let watchCount = model?.watch_total ?? 0
if watchCount > 1000 {
let numStr = NSNumber(floatLiteral: CGFloat(watchCount) / 1000).toString(maximumFractionDigits: 1)
hotView.setTitle("\(numStr)K", for: .normal)
} else {
hotView.setTitle("\(watchCount)", for: .normal)
}
}
}
private lazy var coverImageView: VPImageView = {
let imageView = VPImageView()
return imageView
}()
private lazy var bottomView: UIView = {
let view = VPGradientView()
view.locations = [0, 1]
view.colors = [UIColor.color000000(alpha: 0).cgColor, UIColor.color000000().cgColor]
view.startPoint = .init(x: 0.5, y: 0)
view.endPoint = .init(x: 0.5, y: 1)
return view
}()
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .fontMedium(ofSize: 12)
label.textColor = .colorFFFFFF()
return label
}()
private lazy var hotView: UIButton = {
let button = JXButton(type: .custom)
button.isUserInteractionEnabled = false
button.jx_font = .fontRegular(ofSize: 10)
button.setTitleColor(.colorAFAFAF(), for: .normal)
button.setImage(UIImage(named: "hot_icon_01"), for: .normal)
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
vp_setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension VPHomeBannerCell {
private func vp_setupUI() {
contentView.addSubview(coverImageView)
contentView.addSubview(bottomView)
contentView.addSubview(hotView)
contentView.addSubview(titleLabel)
coverImageView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
bottomView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.height.equalTo(62)
}
hotView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(10)
make.bottom.equalToSuperview().offset(-11)
}
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(10)
make.bottom.equalToSuperview().offset(-27)
make.right.lessThanOrEqualToSuperview().offset(-100)
}
}
}

View File

@ -0,0 +1,74 @@
//
// VPHomeBannerContentCell.swift
// Veloria
//
// Created by Veloria on 2025/5/20.
//
import UIKit
class VPHomeBannerContentCell: VPHomeItemContentCell {
override class var moduleKey: VPHomeModuleItem.ModuleKey {
return .banner
}
override var item: VPHomeModuleItem? {
didSet {
bannerView.reloadData()
}
}
private lazy var bannerView: ZKCycleScrollView = {
let view = ZKCycleScrollView(frame: .zero, shouldInfiniteLoop: true)
view.delegate = self
view.dataSource = self
view.hidesPageControl = true
view.layer.cornerRadius = 10
view.layer.masksToBounds = true
view.register(VPHomeBannerCell.self, forCellWithReuseIdentifier: "cell")
return view
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
vp_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension VPHomeBannerContentCell {
private func vp_setupUI() {
containerView.addSubview(bannerView)
bannerView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
make.centerX.equalToSuperview()
make.top.equalToSuperview()
make.bottom.equalToSuperview()
make.height.equalTo(182)
}
}
}
//MARK: -------------- ZKCycleScrollViewDataSource ZKCycleScrollViewDelegate --------------
extension VPHomeBannerContentCell: ZKCycleScrollViewDataSource, ZKCycleScrollViewDelegate {
func numberOfItems(in cycleScrollView: ZKCycleScrollView) -> Int {
return self.item?.list?.count ?? 0
}
func cycleScrollView(_ cycleScrollView: ZKCycleScrollView, cellForItemAt index: Int) -> ZKCycleScrollViewCell {
let cell = cycleScrollView.dequeueReusableCell(withReuseIdentifier: "cell", for: index) as! VPHomeBannerCell
cell.model = self.item?.list?[index]
return cell
}
}

View File

@ -0,0 +1,39 @@
//
// VPHomeItemContentCell.swift
// Veloria
//
// Created by Veloria on 2025/5/20.
//
import UIKit
class VPHomeItemContentCell: VPTableViewCell {
class var moduleKey: VPHomeModuleItem.ModuleKey {
return .banner
}
var item: VPHomeModuleItem?
private(set) lazy var containerView: UIView = {
let view = UIView()
return view
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(containerView)
containerView.snp.makeConstraints { make in
make.left.right.top.equalToSuperview()
make.bottom.equalToSuperview().offset(-40)
}
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View File

@ -0,0 +1,85 @@
//
// VPHomeRecommandContentCell.swift
// Veloria
//
// Created by Veloria on 2025/5/20.
//
import UIKit
class VPHomeRecommandContentCell: VPHomeItemContentCell {
override class var moduleKey: VPHomeModuleItem.ModuleKey {
return .v3_recommand
}
override var item: VPHomeModuleItem? {
didSet {
titleLabel.text = item?.title
collectionView.reloadData()
}
}
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .fontMedium(ofSize: 15)
label.textColor = .colorFFFFFF()
return label
}()
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.itemSize = CGSize(width: 138, height: 152 + 30)
return layout
}()
private lazy var collectionView: VPCollectionView = {
let collectionView = VPCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
return collectionView
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
vp_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension VPHomeRecommandContentCell {
private func vp_setupUI() {
containerView.addSubview(titleLabel)
containerView.addSubview(collectionView)
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
make.top.equalToSuperview()
}
collectionView.snp.makeConstraints { make in
make.left.right.equalToSuperview()
make.bottom.equalToSuperview()
make.top.equalToSuperview().offset(32)
make.height.equalTo(collectionViewLayout.itemSize.height)
}
}
}
//MARK: -------------- UICollectionViewDelegate UICollectionViewDataSource --------------
//extension VPHomeRecommandContentCell: UICollectionViewDelegate, UICollectionViewDataSource {
//
// func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//
// }
//
// func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// return self.item?.list?.count ?? 0
// }
//}

View File

@ -0,0 +1,56 @@
//
// VPHomeViewModel.swift
// Veloria
//
// Created by Veloria on 2025/5/20.
//
import UIKit
class VPHomeViewModel: VPModel {
var homeDataModel: VPHomeDataModel? {
didSet {
updateDataList()
}
}
/// 使
var oldModuleList: [VPHomeModuleItem]? {
didSet {
filterModuleData()
}
}
///
private(set) lazy var newModuleList: [VPHomeModuleItem] = []
private(set) lazy var homeDataList: [VPHomeDataItem] = []
///
private func filterModuleData() {
newModuleList.removeAll()
oldModuleList?.forEach({
if let key = $0.module_key {
if key == .banner {
newModuleList.insert($0, at: 0)
} else {
newModuleList.append($0)
}
}
})
}
private func updateDataList() {
homeDataList.removeAll()
if let bannerData = homeDataModel?.bannerData {
let item = VPHomeDataItem()
item.type = .banner
item.list = bannerData
}
}
}

View File

@ -0,0 +1,52 @@
//
// VPShortModel.swift
// Veloria
//
// Created by Veloria on 2025/5/20.
//
import UIKit
import SmartCodable
class VPShortModel: VPModel, SmartCodable {
enum TagType: String, SmartCaseDefaultable {
case hot = "hot"
case new = "new"
}
var id: String?
var all_coins: String?
var buy_type: String?
var collect_total: Int?
var sp_description: String?
var episode_total: Int?
var horizontally_img: String?
var image_url: String?
var is_collect: Bool?
var name: String?
var process: String?
var search_click_total: String?
var short_id: String?
var short_play_id: String?
var short_play_video_id: String?
var tag_type: TagType?
var video_info: VPVideoInfoModel?
var category: [String]?
///
var watch_total: Int?
var current_episode: String?
var video_url: String?
@IgnoredKey
var titleAttributedString: NSAttributedString?
@IgnoredKey
var sp_isSelected: Bool?
static func mappingForKey() -> [SmartKeyTransformer]? {
return [
CodingKeys.sp_description <--- ["description"]
]
}
}

View File

@ -0,0 +1,30 @@
//
// VPVideoInfoModel.swift
// Veloria
//
// Created by Veloria on 2025/5/20.
//
import UIKit
import SmartCodable
class VPVideoInfoModel: VPModel, SmartCodable {
var coins: Int?
var vip_coins: Int?
var episode: String?
var id: String?
var image_url: String?
///12:
var is_vip: Int?
///
var is_lock: Bool?
var promise_view_ad: Int?
// var revolution: []
var short_id: String?
var short_play_id: String?
var short_play_video_id: String?
var video_url: String?
///
var play_seconds: Int?
}

View File

@ -10,4 +10,51 @@ import UIKit
class VPAppTool: NSObject {
static var windowScene: UIWindowScene?
static var keyWindow: UIWindow? {
return windowScene?.keyWindow
}
static var rootViewController: UIViewController? {
return keyWindow?.rootViewController
}
static var topViewController: UIViewController? {
var resultVC: UIViewController? = self.rootViewController
if let rootNav = resultVC as? UINavigationController {
resultVC = rootNav.topViewController
}
resultVC = self._topViewController(resultVC)
while resultVC?.presentedViewController != nil {
resultVC = self._topViewController(resultVC?.presentedViewController)
}
return resultVC
}
private static func _topViewController(_ vc: UIViewController?) -> UIViewController? {
if vc is UINavigationController {
return _topViewController((vc as? UINavigationController)?.topViewController)
} else if vc is UITabBarController {
return _topViewController((vc as? UITabBarController)?.selectedViewController)
} else {
return vc
}
}
}
extension VPAppTool {
///
static func openApnsSetting() {
if #available(iOS 16.0, *) {
if let url = URL(string: UIApplication.openNotificationSettingsURLString) {
UIApplication.shared.open(url)
}
} else {
if let url = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(url)
}
}
}
}

View File

@ -0,0 +1,23 @@
//
// VPHUD.swift
// Veloria
//
// Created by Veloria on 2025/5/20.
//
import UIKit
class VPHUD: NSObject {
static func config() {
// SVProgressHUD.setDefaultMaskType(.clear)
}
static func show() {
// SVProgressHUD.setDefaultMaskType(.clear)
// SVProgressHUD.show()
}
static func dismiss() {
// SVProgressHUD.dismiss()
}
}

View File

@ -0,0 +1,22 @@
//
// VPToast.swift
// Veloria
//
// Created by Veloria on 2025/5/20.
//
import UIKit
class VPToast: NSObject {
static func config() {
CSToastManager.setTapToDismissEnabled(false)
CSToastManager.setDefaultDuration(2)
CSToastManager.setDefaultPosition(CSToastPositionCenter)
}
static func show(text: String?) {
guard let text = text else { return }
// SPAPPTool.getKeyWindow()?.makeToast(text)
}
}

View File

@ -0,0 +1,25 @@
//
// VPLoginManager.swift
// Veloria
//
// Created by Veloria on 2025/5/20.
//
import UIKit
class VPLoginManager: NSObject {
static let manager = VPLoginManager()
private(set) var token: VPTokenModel?
///token
private(set) var isRefreshingToken = false
func setLoginToken(token: VPTokenModel?) {
self.token = token
// UserDefaults.jx_setObject(token, forKey: kSPLoginTokenDefaultsKey)
}
}

View File

@ -0,0 +1,41 @@
//
// VPTokenModel.swift
// Veloria
//
// Created by Veloria on 2025/5/20.
//
import UIKit
import SmartCodable
class VPTokenModel: VPModel, SmartCodable, NSSecureCoding {
var token: String?
var customer_id: String?
var auto_login: Int?
required init() { }
static var supportsSecureCoding: Bool {
get {
return true
}
}
func encode(with coder: NSCoder) {
coder.encode(token, forKey: "token")
coder.encode(customer_id, forKey: "customer_id")
coder.encode(auto_login, forKey: "auto_login")
}
required init?(coder: NSCoder) {
super.init()
token = coder.decodeObject(of: NSString.self, forKey: "token") as? String
customer_id = coder.decodeObject(of: NSString.self, forKey: "customer_id") as? String
auto_login = coder.decodeObject(of: NSNumber.self, forKey: "auto_login")?.intValue
}
}

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: 891 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

View File

@ -3,3 +3,6 @@
//
#import <YYKit/YYKit.h>
#import <WMZPageController/WMZPageController.h>
#import "JXUUID.h"
#import <Toast.h>

View File

@ -7,3 +7,4 @@
*/
"Home" = "Home";
"All" = "All";

View File

@ -0,0 +1,252 @@
//
// JXButton.swift
// BoJia
//
// Created by on 2024/5/31.
//
import UIKit
class JXButton: UIButton {
lazy var jx_font: UIFont? = self.titleLabel?.font {
didSet {
self.titleLabel?.font = jx_font
}
}
var maxTitleWidth: CGFloat = 0
var titleDirection: UITextLayoutDirection?
///
var leftAndRightMargin: CGFloat = 0
///
var space: CGFloat = 0
private var imageRect: CGRect = .zero
private var titleRect: CGRect = .zero
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
var gradientLayer: CAGradientLayer {
return self.layer as! CAGradientLayer
}
var locations: [NSNumber]? {
didSet {
self.gradientLayer.locations = locations
}
}
var colors: [CGColor]? {
didSet {
self.gradientLayer.colors = colors
}
}
var startPoint: CGPoint = .zero {
didSet {
self.gradientLayer.startPoint = startPoint
}
}
var endPoint: CGPoint = .zero {
didSet {
self.gradientLayer.endPoint = endPoint
}
}
override var intrinsicContentSize: CGSize {
var width: CGFloat = 0
var height: CGFloat = 0
switch titleDirection {
case .left:
width = imageRect.width + titleRect.width + space
if imageRect.height > titleRect.height {
height = imageRect.height
} else {
height = titleRect.height
}
case .up:
if imageRect.width > titleRect.width {
width = imageRect.width
} else {
width = titleRect.width
}
height = titleRect.height + imageRect.height + space
case .down:
if imageRect.width > titleRect.width {
width = imageRect.width
} else {
width = titleRect.width
}
height = titleRect.height + imageRect.height + space
default:
width = imageRect.width + titleRect.width + space
if imageRect.height > titleRect.height {
height = imageRect.height
} else {
height = titleRect.height
}
}
let size = CGSize(width: width + leftAndRightMargin * 2, height: height)
return size
}
override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
let imageSize = currentImage?.size ?? .zero
let textSize = self._textSize()
let contentWidth = self.frame.size.width
let contentHeight = self.frame.size.height
var x: CGFloat = 0
var y: CGFloat = 0
var width: CGFloat = 0
var height: CGFloat = 0
switch titleDirection {
case .left:
x = (contentWidth - space) / 2 - imageSize.width / 2 + textSize.width / 2 + space
y = contentHeight / 2 - imageSize.height / 2
width = imageSize.width
height = imageSize.height
case .up:
x = contentWidth / 2 - imageSize.width / 2
y = (contentHeight - space) / 2 - imageSize.height / 2 + textSize.height / 2 + space
width = imageSize.width
height = imageSize.height
case .down:
x = contentWidth / 2 - imageSize.width / 2
y = (contentHeight - space) / 2 - imageSize.height / 2 - textSize.height / 2
width = imageSize.width
height = imageSize.height
default:
x = (contentWidth - space) / 2 - imageSize.width / 2 - textSize.width / 2
y = contentHeight / 2 - imageSize.height / 2
width = imageSize.width
height = imageSize.height
}
self.imageRect = CGRect(x: x, y: y, width: width, height: height)
return self.imageRect
}
override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
let imageSize = currentImage?.size ?? .zero
let textSize = self._textSize()
let contentWidth = self.frame.size.width
let contentHeight = self.frame.size.height
var x: CGFloat = 0
var y: CGFloat = 0
var width: CGFloat = 0
var height: CGFloat = 0
switch titleDirection {
case .left:
x = (contentWidth - space) / 2 - imageSize.width / 2 - textSize.width / 2
y = contentHeight / 2 - textSize.height / 2
width = textSize.width
height = textSize.height
case .up:
x = contentWidth / 2 - textSize.width / 2
y = (contentHeight - space) / 2 - textSize.height / 2 - imageSize.height / 2
width = textSize.width
height = textSize.height
case .down:
x = contentWidth / 2 - textSize.width / 2
y = (contentHeight - space) / 2 - textSize.height / 2 + imageSize.height / 2 + space
width = textSize.width
height = textSize.height
default:
x = (contentWidth - space) / 2 + imageSize.width / 2 - textSize.width / 2 + space
y = contentHeight / 2 - textSize.height / 2
width = textSize.width
height = textSize.height
}
self.titleRect = CGRect(x: x, y: y, width: width, height: height)
return self.titleRect
}
private var borderColors: [UInt : UIColor] = [:]
func jx_setBorderColor(_ color: UIColor?, for state: UIControl.State) {
var arr = self.borderColors
let index = state.rawValue
arr[index] = color
if state == .selected {
let i = index + UIControl.State.highlighted.rawValue
arr[i] = color
}
self.borderColors = arr
updateStatus()
}
func updateStatus() {
var color = self.borderColors[self.state.rawValue]?.cgColor
if (color == nil) {
color = self.borderColors[UIControl.State.normal.rawValue]?.cgColor
}
self.layer.borderColor = color
}
override var isSelected: Bool {
didSet {
updateStatus()
}
}
override var isEnabled: Bool {
didSet {
updateStatus()
}
}
}
extension JXButton {
private func _textSize() -> CGSize {
// let size = CGSize(width: self.bounds.size.width, height: self.bounds.size.height)
var attr: [NSAttributedString.Key : Any] = [:]
attr[NSAttributedString.Key.font] = jx_font
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineBreakMode = .byTruncatingMiddle
attr[NSAttributedString.Key.paragraphStyle] = paragraphStyle
// var rect = self.currentTitle?.ocString.boundingRect(with: size, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: attr, context: nil) ?? .zero
if let font = jx_font {
var size = self.currentTitle?.size(font: font) ?? .zero
if maxTitleWidth != 0 && maxTitleWidth < size.width {
size = CGSize(width: maxTitleWidth, height: size.height)
}
// if maxTitleWidth != 0 && maxTitleWidth < rect.size.width {
// rect.size = CGSize(width: maxTitleWidth, height: rect.size.height)
// }
return size
} else {
return .zero
}
}
}

20
Veloria/Thirdparty/JXUUID/JXUUID.h vendored Normal file
View File

@ -0,0 +1,20 @@
//
// JXUUID.h
// 设备标识符
//
// Created by 曾觉新 on 2017/8/24.
// Copyright © 2017年 曾觉新. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface JXUUID : NSObject
+ (nonnull NSString *)uuid;
+ (nonnull NSString *)idfa;
/**
app后
*/
+ (nonnull NSString *)systemUUID;
@end

47
Veloria/Thirdparty/JXUUID/JXUUID.m vendored Normal file
View File

@ -0,0 +1,47 @@
//
// JXUUID.m
//
//
// Created by on 2017/8/24.
// Copyright © 2017 . All rights reserved.
//
#import "JXUUID.h"
#import <UIKit/UIKit.h>
#import "PDKeyChain.h"
#import <AdSupport/AdSupport.h>
static NSString *const uuidKey = @"com.JXUUID";
@implementation JXUUID
+ (nonnull NSString *)uuid
{
static NSString *uuid;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
uuid = [PDKeyChain objectForKey:uuidKey];
if (uuid && uuid.length > 0) {
} else {
uuid = [[NSUUID UUID] UUIDString];
[PDKeyChain setObject:uuid forKey:uuidKey];
}
});
return uuid;
}
+ (nonnull NSString *)idfa
{
static NSString *idfa;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
idfa = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
});
return idfa;
}
+ (nonnull NSString *)systemUUID
{
return [UIDevice currentDevice].identifierForVendor.UUIDString;
}
@end

31
Veloria/Thirdparty/JXUUID/PDKeyChain.h vendored Executable file
View File

@ -0,0 +1,31 @@
//
// PDKeyChain.h
// PDKeyChain
//
// Created by Panda on 16/8/23.
// Copyright © 2016年 v2panda. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <Security/Security.h>
@interface PDKeyChain : NSObject
/**
* KeyChain
*
* @return NSDictionary
*/
+ (NSDictionary *)getKeyChainData;
+ (id)objectForKey:(NSString *)key;
+ (void)setObject:(id)object forKey:(NSString *)key;
+ (void)removeObjectForKey:(NSString *)key;
+ (void)removeAllObjects;
/**
* KeyChain
*/
+ (void)keyChainDelete;
@end

100
Veloria/Thirdparty/JXUUID/PDKeyChain.m vendored Executable file
View File

@ -0,0 +1,100 @@
//
// PDKeyChain.m
// PDKeyChain
//
// Created by Panda on 16/8/23.
// Copyright © 2016 v2panda. All rights reserved.
//
#import "PDKeyChain.h"
static NSString * const kPDKeyChainKey = @"com.veloria.keychainKey";
@implementation PDKeyChain
+ (void)keyChainDelete{
[self delete:kPDKeyChainKey];
}
+ (NSDictionary *)getKeyChainData
{
NSDictionary *dic = [self load:kPDKeyChainKey];
if (!dic) {
dic = [NSDictionary dictionary];
}
return dic;
}
+ (void)setObject:(id)object forKey:(NSString *)key
{
NSMutableDictionary *tempDic = [[self getKeyChainData] mutableCopy];
[tempDic setObject:object forKey:key];
[self save:kPDKeyChainKey data:tempDic];
}
+ (id)objectForKey:(NSString *)key
{
NSDictionary *tempDic = [self getKeyChainData];
return tempDic[key];
}
+ (void)removeObjectForKey:(NSString *)key
{
NSMutableDictionary *tempDic = [[self getKeyChainData] mutableCopy];
[tempDic removeObjectForKey:key];
[self save:kPDKeyChainKey data:tempDic];
}
+ (void)removeAllObjects
{
NSMutableDictionary *tempDic = [[self getKeyChainData] mutableCopy];
[tempDic removeAllObjects];
[self save:kPDKeyChainKey data:tempDic];
}
+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service {
return [NSMutableDictionary dictionaryWithObjectsAndKeys:
(id)kSecClassGenericPassword,(id)kSecClass,
service, (id)kSecAttrService,
service, (id)kSecAttrAccount,
(id)kSecAttrAccessibleAfterFirstUnlock,(id)kSecAttrAccessible,
nil];
}
+ (void)save:(NSString *)service data:(id)data {
//Get search dictionary
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
//Delete old item before add new item
SecItemDelete((CFDictionaryRef)keychainQuery);
//Add new object to search dictionary(Attention:the data format)
[keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData];
//Add item to keychain with the search dictionary
SecItemAdd((CFDictionaryRef)keychainQuery, NULL);
}
+ (id)load:(NSString *)service {
id ret = nil;
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
//Configure the search setting
//Since in our simple case we are expecting only a single attribute to be returned (the password) we can set the attribute kSecReturnData to kCFBooleanTrue
[keychainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
[keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
CFDataRef keyData = NULL;
if (SecItemCopyMatching((CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {
@try {
ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData];
} @catch (NSException *e) {
NSLog(@"Unarchive of %@ failed: %@", service, e);
} @finally {
}
}
if (keyData)
CFRelease(keyData);
return ret;
}
+ (void)delete:(NSString *)service {
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
SecItemDelete((CFDictionaryRef)keychainQuery);
}
@end

View File

@ -0,0 +1,591 @@
//
// ZKCycleScrollView.swift
// ZKCycleScrollViewDemo
//
// Created by bestdew on 2019/3/8.
// Copyright © 2019 bestdew. All rights reserved.
//
// d*##$.
// zP"""""$e. $" $o
//4$ '$ $" $
//'$ '$ J$ $F
// 'b $k $> $
// $k $r J$ d$
// '$ $ $" $~
// '$ "$ '$E $
// $ $L $" $F ...
// $. 4B $ $$$*"""*b
// '$ $. $$ $$ $F
// "$ R$ $F $" $
// $k ?$ u* dF .$
// ^$. $$" z$ u$$$$e
// #$b $E.dW@e$" ?$
// #$ .o$$# d$$$$c ?F
// $ .d$$#" . zo$> #$r .uF
// $L .u$*" $&$$$k .$$d$$F
// $$" ""^"$$$P"$P9$
// JP .o$$$$u:$P $$
// $ ..ue$" "" $"
// d$ $F $
// $$ ....udE 4B
// #$ """"` $r @$
// ^$L '$ $F
// RN 4N $
// *$b d$
// $$k $F
// $$b $F
// $"" $F
// '$ $
// $L $
// '$ $
// $ $
import UIKit
public typealias ZKCycleScrollViewCell = UICollectionViewCell
public enum ZKScrollDirection: Int {
case horizontal
case vertical
}
@objc public protocol ZKCycleScrollViewDataSource: NSObjectProtocol {
/// Return number of pages
func numberOfItems(in cycleScrollView: ZKCycleScrollView) -> Int
/// The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndex:
func cycleScrollView(_ cycleScrollView: ZKCycleScrollView, cellForItemAt index: Int) -> ZKCycleScrollViewCell
}
@objc public protocol ZKCycleScrollViewDelegate: NSObjectProtocol {
/// Called when the cell is clicked
@objc optional func cycleScrollView(_ cycleScrollView: ZKCycleScrollView, didSelectItemAt index: Int)
/// Called when the offset changes. The progress range is from 0 to the maximum index value, which means the progress value for a round of scrolling
@objc optional func cycleScrollViewDidScroll(_ cycleScrollView: ZKCycleScrollView, progress: Double)
/// Called when scrolling to a new index page
@objc optional func cycleScrollView(_ cycleScrollView: ZKCycleScrollView, didScrollFromIndex fromIndex: Int, toIndex: Int)
}
@IBDesignable open class ZKCycleScrollView: UIView {
@IBOutlet open weak var delegate: ZKCycleScrollViewDelegate?
@IBOutlet open weak var dataSource: ZKCycleScrollViewDataSource?
var currentIdx = 0
var stepLength: CGFloat? //
#if TARGET_INTERFACE_BUILDER
@IBInspectable open var scrollDirection: Int = 0
#else
/// default horizontal. scroll direction
open var scrollDirection: ZKScrollDirection = .horizontal {
didSet {
switch scrollDirection {
case .vertical:
flowLayout?.scrollDirection = .vertical
default:
flowLayout?.scrollDirection = .horizontal
}
}
}
#endif
/// default 3.f. automatic scroll time interval
@IBInspectable open var autoScrollInterval: TimeInterval = 3 {
didSet {
addTimer()
}
}
@IBInspectable open var isAutoScroll: Bool = true {
didSet {
addTimer()
}
}
/// default true. turn off any dragging temporarily
@IBInspectable open var allowsDragging: Bool = true {
didSet {
collectionView.isScrollEnabled = allowsDragging
}
}
/// default the view size
@IBInspectable open var itemSize: CGSize = CGSize.zero {
didSet {
itemSizeFlag = true
flowLayout.itemSize = itemSize
flowLayout.headerReferenceSize = CGSize(width: (bounds.width - itemSize.width) / 2, height: (bounds.height - itemSize.height) / 2)
flowLayout.footerReferenceSize = CGSize(width: (bounds.width - itemSize.width) / 2, height: (bounds.height - itemSize.height) / 2)
}
}
/// default 0.0
@IBInspectable open var itemSpacing: CGFloat = 0.0 {
didSet {
flowLayout.minimumLineSpacing = itemSpacing
}
}
/// default 1.f(no scaling), it ranges from 0.f to 1.f
@IBInspectable open var itemZoomScale: CGFloat = 1.0 {
didSet {
flowLayout.zoomScale = itemZoomScale
}
}
///item
open var itemAlpha = false {
didSet {
flowLayout.itemAlpha = itemAlpha
}
}
///
open var rotationAngle = 0.0 {
didSet {
flowLayout.rotationAngle = rotationAngle
}
}
@IBInspectable open var hidesPageControl: Bool = false {
didSet {
pageControl?.isHidden = hidesPageControl
}
}
@IBInspectable open var pageIndicatorTintColor: UIColor = UIColor.gray {
didSet {
pageControl?.pageIndicatorTintColor = pageIndicatorTintColor
}
}
@IBInspectable open var currentPageIndicatorTintColor: UIColor = UIColor.white {
didSet {
pageControl?.currentPageIndicatorTintColor = currentPageIndicatorTintColor
}
}
/// current page index
open var pageIndex: Int {
return changeIndex(currentIndex())
}
/// current content offset
open var contentOffset: CGPoint {
let num = CGFloat(numberOfAddedCells() / 2)
switch scrollDirection {
case .vertical:
return CGPoint(x: 0.0, y: max(0.0, collectionView.contentOffset.y - (flowLayout.itemSize.height + flowLayout.minimumLineSpacing) * num))
default:
return CGPoint(x: max(0.0, collectionView.contentOffset.x - (flowLayout.itemSize.width + flowLayout.minimumLineSpacing) * num), y: 0.0)
}
}
/// infinite cycle
@IBInspectable open private(set) var isInfiniteLoop: Bool = true
/// load completed callback
open var loadCompletion: (() -> Void)? = nil
open var pageControlFrame: CGRect?
var pageControl: UIPageControl!
var collectionView: UICollectionView!
private var flowLayout: ZKCycleScrollViewFlowLayout!
private var timer: Timer?
private var numberOfItems: Int = 0
private var fromIndex: Int = 0
private var itemSizeFlag: Bool = false
private var indexOffset: Int = 0
private var configuredFlag: Bool = false
private var tempIndex: Int = 0
// MARK: - Open Func
open func register(_ cellClass: AnyClass?, forCellWithReuseIdentifier identifier: String) {
collectionView.register(cellClass, forCellWithReuseIdentifier: identifier)
}
open func register(_ nib: UINib?, forCellWithReuseIdentifier identifier: String) {
collectionView.register(nib, forCellWithReuseIdentifier: identifier)
}
open func dequeueReusableCell(withReuseIdentifier identifier: String, for index: Int) -> ZKCycleScrollViewCell {
let indexPath = IndexPath(item: changeIndex(index), section: 0)
return collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath)
}
open func reloadData() {
removeTimer()
UIView.performWithoutAnimation {
self.collectionView.reloadData()
}
collectionView.performBatchUpdates(nil) { _ in
self.configuration()
self.loadCompletion?()
}
}
/// Call -beginUpdates and -endUpdates to update layout
/// Allows multiple scrollDirection/itemSize/itemSpacing/itemZoomScale to be set simultaneously.
open func beginUpdates() {
tempIndex = pageIndex
removeTimer()
}
open func endUpdates() {
flowLayout.invalidateLayout()
scrollToItem(at: tempIndex, animated: false)
addTimer()
}
/// Scroll to page
open func scrollToItem(at index: Int, animated: Bool) {
let num = numberOfAddedCells()
guard index >= 0 && index <= numberOfItems - 1 - num else {
print("attempt to scroll to invalid index:\(index)")
return
}
removeTimer()
let idx = index + num / 2
let position = scrollPosition()
let indexPath = IndexPath(item: idx, section: 0)
collectionView.scrollToItem(at: indexPath, at: position, animated: animated)
addTimer()
}
/// Returns the visible cell object at the specified index
open func cellForItem(at index: Int) -> ZKCycleScrollViewCell? {
let num = numberOfAddedCells()
guard index >= 0 && index <= numberOfItems - 1 - num else {
return nil
}
let idx = index + num / 2
let indexPath = IndexPath(item: idx, section: 0)
let cell = collectionView.cellForItem(at: indexPath)
return cell
}
// MARK: - Init
public init(frame: CGRect, shouldInfiniteLoop infiniteLoop: Bool? = nil) {
super.init(frame: frame)
isInfiniteLoop = infiniteLoop ?? true
initialization()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialization()
}
override open func layoutSubviews() {
super.layoutSubviews()
if itemSizeFlag {
flowLayout.itemSize = itemSize
flowLayout.headerReferenceSize = CGSize(width: (bounds.width - itemSize.width) / 2, height: (bounds.height - itemSize.height) / 2)
flowLayout.footerReferenceSize = CGSize(width: (bounds.width - itemSize.width) / 2, height: (bounds.height - itemSize.height) / 2)
} else {
flowLayout.itemSize = bounds.size
flowLayout.headerReferenceSize = CGSize.zero
flowLayout.footerReferenceSize = CGSize.zero
}
collectionView.frame = bounds
if let pageControlFrame = pageControlFrame {
pageControl.frame = pageControlFrame
} else {
pageControl.frame = CGRect(x: 0.0, y: bounds.height - 15.0, width: bounds.width, height: 15.0)
}
}
override open func willMove(toSuperview newSuperview: UIView?) {
if newSuperview == nil { removeTimer() }
}
override open func setValue(_ value: Any?, forUndefinedKey key: String) {
guard key == "scrollDirection" else {
return;
}
let direction = value as! Int
if direction == 1 {
scrollDirection = .vertical
} else {
scrollDirection = .horizontal
}
}
deinit {
collectionView.delegate = nil
collectionView.dataSource = nil
}
// MARK: - Private Func
private func initialization() {
flowLayout = ZKCycleScrollViewFlowLayout()
flowLayout.minimumLineSpacing = 0
flowLayout.minimumInteritemSpacing = 0
flowLayout.scrollDirection = .horizontal
collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: flowLayout)
collectionView.backgroundColor = nil
collectionView.delegate = self
collectionView.dataSource = self
collectionView.scrollsToTop = false
collectionView.bounces = false
collectionView.showsVerticalScrollIndicator = false
collectionView.showsHorizontalScrollIndicator = false
addSubview(collectionView)
pageControl = UIPageControl()
pageControl.isEnabled = false
pageControl.hidesForSinglePage = true
pageControl.pageIndicatorTintColor = UIColor.gray
pageControl.currentPageIndicatorTintColor = UIColor.white
addSubview(pageControl);
DispatchQueue.main.async {
self.configuration()
self.loadCompletion?()
}
}
private func configuration() {
fromIndex = 0
indexOffset = 0
configuredFlag = false
guard numberOfItems > 1 else { return }
let position = scrollPosition()
if isInfiniteLoop {
let indexPath = IndexPath(item: 2, section: 0)
collectionView.scrollToItem(at: indexPath, at: position, animated: false)
}
addTimer()
updatePageControl()
configuredFlag = true
}
func addTimer() {
removeTimer()
if numberOfItems < 2 || !isAutoScroll || autoScrollInterval <= 0.0 { return }
timer = Timer.scheduledTimer(timeInterval: autoScrollInterval, target: YYWeakProxy(target: self), selector: #selector(automaticScroll), userInfo: nil, repeats: true)
RunLoop.main.add(timer!, forMode: .common)
}
private func updatePageControl() {
let num = numberOfAddedCells()
pageControl.currentPage = 0
pageControl.numberOfPages = max(0, numberOfItems - num)
pageControl.isHidden = (hidesPageControl || pageControl.numberOfPages < 2)
}
private func numberOfAddedCells() -> Int {
return isInfiniteLoop ? 4 : 0
}
@objc private func automaticScroll() {
var index = currentIndex() + 1
if !isInfiniteLoop && index >= numberOfItems {
index = 0
}
if let stepLength = stepLength {
let offsetX = self.collectionView.contentOffset.x
let width = flowLayout.itemSize.width + flowLayout.minimumLineSpacing
self.collectionView.contentOffset = CGPoint(x: offsetX+stepLength, y: self.contentOffset.y)
let position = scrollPosition()
if self.currentIndex() == 1 {
let indexPath = IndexPath(item: numberOfItems - 3, section: 0)
collectionView.scrollToItem(at: indexPath, at: position, animated: false)
} else if self.currentIndex() == numberOfItems - 2 {
let offsetx = (width)*CGFloat(2) - width*0.5
self.collectionView.contentOffset = CGPoint(x: offsetx, y: self.contentOffset.y)
self.collectionView.reloadData()
}
} else {
let position = scrollPosition()
let indexPath = IndexPath(item: index, section: 0)
collectionView.scrollToItem(at: indexPath, at: position, animated: true)
}
}
func removeTimer() {
timer?.invalidate()
timer = nil
}
private func scrollPosition() -> UICollectionView.ScrollPosition {
switch scrollDirection {
case .vertical:
return .centeredVertically
default:
return .centeredHorizontally
}
}
private func currentIndex() -> Int {
guard numberOfItems > 0 else {
return -1
}
var index = 0
var minimumIndex = 0
var maximumIndex = numberOfItems - 1
if numberOfItems == 1 {
return index
}
if isInfiniteLoop {
minimumIndex = 1
maximumIndex = numberOfItems - 2
}
switch scrollDirection {
case .vertical:
let height = flowLayout.itemSize.height + flowLayout.minimumLineSpacing
index = Int((collectionView.contentOffset.y + height / 2) / height)
default:
let width = flowLayout.itemSize.width + flowLayout.minimumLineSpacing
index = Int((collectionView.contentOffset.x + width / 2) / width)
}
return min(maximumIndex, max(minimumIndex, index))
}
private func changeIndex(_ index: Int) -> Int {
guard isInfiniteLoop && numberOfItems > 1 else {
return index
}
var idx = index
if index == 0 {
idx = numberOfItems - 6
} else if index == 1 {
idx = numberOfItems - 5
} else if index == numberOfItems - 2 {
idx = 0
} else if index == numberOfItems - 1 {
idx = 1
} else {
idx = index - 2
}
return idx
}
}
extension ZKCycleScrollView: UICollectionViewDelegate {
public func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool {
return true
}
public func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath) {
if let delegate = delegate, delegate.responds(to: #selector(ZKCycleScrollViewDelegate.cycleScrollView(_:didSelectItemAt:))) {
removeTimer()
}
}
public func collectionView(_ collectionView: UICollectionView, didUnhighlightItemAt indexPath: IndexPath) {
if let delegate = delegate, delegate.responds(to: #selector(ZKCycleScrollViewDelegate.cycleScrollView(_:didSelectItemAt:))) {
addTimer()
}
}
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let delegate = delegate, delegate.responds(to: #selector(ZKCycleScrollViewDelegate.cycleScrollView(_:didSelectItemAt:))) {
delegate.cycleScrollView!(self, didSelectItemAt: changeIndex(indexPath.item))
}
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
pageControl.currentPage = pageIndex
if let delegate = delegate, delegate.responds(to: #selector(ZKCycleScrollViewDelegate.cycleScrollViewDidScroll(_:progress:))) {
var total: CGFloat = 0.0
var offset: CGFloat = 0.0
let num = numberOfAddedCells()
switch scrollDirection {
case .vertical:
total = CGFloat(numberOfItems - 1 - num) * (flowLayout.itemSize.height + flowLayout.minimumLineSpacing)
offset = contentOffset.y.truncatingRemainder(dividingBy:((flowLayout.itemSize.height + flowLayout.minimumLineSpacing) * CGFloat(numberOfItems - num)))
default:
total = CGFloat(numberOfItems - 1 - num) * (flowLayout.itemSize.width + flowLayout.minimumLineSpacing)
offset = contentOffset.x.truncatingRemainder(dividingBy:((flowLayout.itemSize.width + flowLayout.minimumLineSpacing) * CGFloat(numberOfItems - num)))
}
let percent = Double(offset / total)
let progress = percent * Double(numberOfItems - 1 - num)
delegate.cycleScrollViewDidScroll!(self, progress: progress)
}
}
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
removeTimer()
}
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if let _ = stepLength {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.addTimer()
}
} else {
addTimer()
}
}
public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
let index = currentIndex()
if isInfiniteLoop {
let position = scrollPosition()
if index == 1 {
let indexPath = IndexPath(item: numberOfItems - 3, section: 0)
collectionView.scrollToItem(at: indexPath, at: position, animated: false)
} else if index == numberOfItems - 2 {
let indexPath = IndexPath(item: 2, section: 0)
collectionView.scrollToItem(at: indexPath, at: position, animated: false)
}
}
let toIndex = changeIndex(index)
if let delegate = delegate, delegate.responds(to: #selector(ZKCycleScrollViewDelegate.cycleScrollView(_:didScrollFromIndex:toIndex:))) {
delegate.cycleScrollView!(self, didScrollFromIndex: fromIndex, toIndex: toIndex)
}
fromIndex = toIndex
}
public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
guard pageIndex == fromIndex else { return }
let sum = velocity.x + velocity.y
if sum > 0 {
indexOffset = 1
} else if sum < 0 {
indexOffset = -1
} else {
indexOffset = 0
}
}
public func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
let position = scrollPosition()
var index = currentIndex() + indexOffset
index = max(0, index)
index = min(numberOfItems - 1, index)
let indexPath = IndexPath(item: index, section: 0)
collectionView.scrollToItem(at: indexPath, at: position, animated: true)
indexOffset = 0
}
}
extension ZKCycleScrollView: UICollectionViewDataSource {
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
numberOfItems = dataSource?.numberOfItems(in: self) ?? 0
if isInfiniteLoop && numberOfItems > 1 {
numberOfItems += numberOfAddedCells()
}
return numberOfItems
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let index = changeIndex(indexPath.item)
return (dataSource?.cycleScrollView(self, cellForItemAt: index))!
}
}

View File

@ -0,0 +1,168 @@
//
// ZKCycleScrollViewFlowLayout.swift
// ZKCycleScrollViewDemo
//
// Created by bestdew on 2019/3/22.
// Copyright © 2019 bestdew. All rights reserved.
//
// d*##$.
// zP"""""$e. $" $o
//4$ '$ $" $
//'$ '$ J$ $F
// 'b $k $> $
// $k $r J$ d$
// '$ $ $" $~
// '$ "$ '$E $
// $ $L $" $F ...
// $. 4B $ $$$*"""*b
// '$ $. $$ $$ $F
// "$ R$ $F $" $
// $k ?$ u* dF .$
// ^$. $$" z$ u$$$$e
// #$b $E.dW@e$" ?$
// #$ .o$$# d$$$$c ?F
// $ .d$$#" . zo$> #$r .uF
// $L .u$*" $&$$$k .$$d$$F
// $$" ""^"$$$P"$P9$
// JP .o$$$$u:$P $$
// $ ..ue$" "" $"
// d$ $F $
// $$ ....udE 4B
// #$ """"` $r @$
// ^$L '$ $F
// RN 4N $
// *$b d$
// $$k $F
// $$b $F
// $"" $F
// '$ $
// $L $
// '$ $
// $ $
import UIKit
open class ZKCycleScrollViewFlowLayout: UICollectionViewFlowLayout {
open var zoomScale: CGFloat = 1.0
///item
open var itemAlpha = false
///
open var rotationAngle = 0.0
override open func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
override open func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let attributes: [UICollectionViewLayoutAttributes] = NSArray(array: super.layoutAttributesForElements(in: rect) ?? [], copyItems: true) as! [UICollectionViewLayoutAttributes]
if let collectionView = collectionView {
switch scrollDirection {
case .vertical:
let offset = collectionView.bounds.midY;
let distanceForScale = itemSize.height + minimumLineSpacing;
for attr in attributes {
var scale: CGFloat = 0.0;
let distance = abs(offset - attr.center.y)
if distance >= distanceForScale {
scale = zoomScale;
} else if distance == 0.0 {
scale = 1.0
attr.zIndex = 1
} else {
scale = zoomScale + (distanceForScale - distance) * (1.0 - zoomScale) / distanceForScale
}
attr.transform = CGAffineTransform(scaleX: scale, y: scale)
}
default:
let offset = collectionView.bounds.midX;
let distanceForScale = itemSize.width + minimumLineSpacing;
for attr in attributes {
var scale: CGFloat = 0.0;
let distance = offset - attr.center.x
let abs_distance = abs(distance)
let screenScale = distance / (collectionView.bounds.width)
if abs_distance >= distanceForScale {
scale = zoomScale;
} else if abs_distance == 0.0 {
scale = 1.0
attr.zIndex = 1
} else {
scale = zoomScale + (distanceForScale - abs_distance) * (1.0 - zoomScale) / distanceForScale
}
if itemAlpha {
var alpha = 1 - abs(screenScale) + 0.1
if alpha < 0 {
alpha = 0
} else if alpha > 1 {
alpha = 1
}
attr.alpha = alpha
} else {
attr.alpha = 1
}
//
let scaleTransform = CGAffineTransform(scaleX: scale, y: scale)
//
// let rotationAngle = kSPAngleToRadians(angle: -screenScale * self.rotationAngle)
// let rotationTransform = CGAffineTransform(rotationAngle: rotationAngle)
//
// //
// let combinedTransform = rotationTransform.concatenating(scaleTransform)
//
attr.transform = scaleTransform
}
}
}
return attributes
}
override open func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
var point = proposedContentOffset
if let collectionView = collectionView {
switch scrollDirection {
case .vertical:
///
let rect = CGRect(x: 0.0, y: point.y, width: collectionView.bounds.width, height: collectionView.bounds.height)
/// collectionViewy
let centerY = point.y + collectionView.bounds.size.height * 0.5
///
var minDelta = CGFloat(MAXFLOAT)
/// super
if let attributes = super.layoutAttributesForElements(in: rect) {
for attr in attributes {
if abs(minDelta) > abs(attr.center.y - centerY) {
minDelta = attr.center.y - centerY
}
}
}
///
point.y += minDelta
default:
///
let rect = CGRect(x: point.x, y: 0.0, width: collectionView.bounds.width, height: collectionView.bounds.height)
/// collectionViewy
let centerX = point.x + collectionView.bounds.size.width * 0.5
///
var minDelta = CGFloat(MAXFLOAT)
/// super
if let attributes = super.layoutAttributesForElements(in: rect) {
for attr in attributes {
if abs(minDelta) > abs(attr.center.x - centerX) {
minDelta = attr.center.x - centerX
}
}
}
///
point.x += minDelta
}
}
return point
}
}