首页开发,tabbar样式开发,网络框架搭建
This commit is contained in:
parent
54b5f08e23
commit
a687f01794
1
Podfile
1
Podfile
@ -27,5 +27,6 @@ target 'Veloria' do
|
||||
pod 'HWPanModal' #底部弹出控制器
|
||||
pod 'Kingfisher' #图片加载
|
||||
pod "ESTabBarController-swift"
|
||||
pod 'WMZPageController' #分页控制器
|
||||
|
||||
end
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
25
Veloria/Base/Extension/NSNumber+VPAdd.swift
Normal file
25
Veloria/Base/Extension/NSNumber+VPAdd.swift
Normal 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"
|
||||
}
|
||||
}
|
33
Veloria/Base/Extension/String+VPAdd.swift
Normal file
33
Veloria/Base/Extension/String+VPAdd.swift
Normal 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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
24
Veloria/Base/Extension/UIImageView+VPAdd.swift
Normal file
24
Veloria/Base/Extension/UIImageView+VPAdd.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
22
Veloria/Base/Model/VPListModel.swift
Normal file
22
Veloria/Base/Model/VPListModel.swift
Normal 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?
|
||||
}
|
@ -9,7 +9,7 @@ import UIKit
|
||||
|
||||
class VPModel: NSObject {
|
||||
|
||||
override init() {
|
||||
required override init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
|
22
Veloria/Base/Networking/API/VPHomeAPI.swift
Normal file
22
Veloria/Base/Networking/API/VPHomeAPI.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
116
Veloria/Base/Networking/Base/VPApi.swift
Normal file
116
Veloria/Base/Networking/Base/VPApi.swift
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
224
Veloria/Base/Networking/Base/VPNetwork.swift
Normal file
224
Veloria/Base/Networking/Base/VPNetwork.swift
Normal 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
|
||||
}
|
46
Veloria/Base/Networking/Base/VPURLPath.swift
Normal file
46
Veloria/Base/Networking/Base/VPURLPath.swift
Normal 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
|
22
Veloria/Base/View/VPCollectionView.swift
Normal file
22
Veloria/Base/View/VPCollectionView.swift
Normal 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")
|
||||
}
|
||||
|
||||
}
|
54
Veloria/Base/View/VPImageView.swift
Normal file
54
Veloria/Base/View/VPImageView.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
43
Veloria/Base/View/VPTableViewCell.swift
Normal file
43
Veloria/Base/View/VPTableViewCell.swift
Normal 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
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
20
Veloria/Class/Home/Model/VPHomeDataItem.swift
Normal file
20
Veloria/Class/Home/Model/VPHomeDataItem.swift
Normal 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]?
|
||||
|
||||
|
||||
}
|
27
Veloria/Class/Home/Model/VPHomeDataModel.swift
Normal file
27
Veloria/Class/Home/Model/VPHomeDataModel.swift
Normal 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?
|
||||
}
|
38
Veloria/Class/Home/Model/VPHomeModuleItem.swift
Normal file
38
Veloria/Class/Home/Model/VPHomeModuleItem.swift
Normal 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]])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
98
Veloria/Class/Home/View/VPHomeBannerCell.swift
Normal file
98
Veloria/Class/Home/View/VPHomeBannerCell.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
74
Veloria/Class/Home/View/VPHomeBannerContentCell.swift
Normal file
74
Veloria/Class/Home/View/VPHomeBannerContentCell.swift
Normal 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
|
||||
}
|
||||
}
|
39
Veloria/Class/Home/View/VPHomeItemContentCell.swift
Normal file
39
Veloria/Class/Home/View/VPHomeItemContentCell.swift
Normal 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")
|
||||
}
|
||||
|
||||
}
|
85
Veloria/Class/Home/View/VPHomeRecommandContentCell.swift
Normal file
85
Veloria/Class/Home/View/VPHomeRecommandContentCell.swift
Normal 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
|
||||
// }
|
||||
//}
|
56
Veloria/Class/Home/ViewModel/VPHomeViewModel.swift
Normal file
56
Veloria/Class/Home/ViewModel/VPHomeViewModel.swift
Normal 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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
52
Veloria/Class/Player/Model/VPShortModel.swift
Normal file
52
Veloria/Class/Player/Model/VPShortModel.swift
Normal 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"]
|
||||
]
|
||||
}
|
||||
}
|
30
Veloria/Class/Player/Model/VPVideoInfoModel.swift
Normal file
30
Veloria/Class/Player/Model/VPVideoInfoModel.swift
Normal 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?
|
||||
///1:会员,2: 非会员
|
||||
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?
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
23
Veloria/Libs/HUD/VPHUD.swift
Normal file
23
Veloria/Libs/HUD/VPHUD.swift
Normal 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()
|
||||
}
|
||||
}
|
22
Veloria/Libs/HUD/VPToast.swift
Normal file
22
Veloria/Libs/HUD/VPToast.swift
Normal 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)
|
||||
}
|
||||
}
|
25
Veloria/Libs/Login/VPLoginManager.swift
Normal file
25
Veloria/Libs/Login/VPLoginManager.swift
Normal 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)
|
||||
}
|
||||
}
|
41
Veloria/Libs/Login/VPTokenModel.swift
Normal file
41
Veloria/Libs/Login/VPTokenModel.swift
Normal 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
|
||||
}
|
||||
|
||||
|
||||
}
|
22
Veloria/Source/Assets.xcassets/icon/hot_icon_01.imageset/Contents.json
vendored
Normal file
22
Veloria/Source/Assets.xcassets/icon/hot_icon_01.imageset/Contents.json
vendored
Normal 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
|
||||
}
|
||||
}
|
BIN
Veloria/Source/Assets.xcassets/icon/hot_icon_01.imageset/Frame@2x.png
vendored
Normal file
BIN
Veloria/Source/Assets.xcassets/icon/hot_icon_01.imageset/Frame@2x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 891 B |
BIN
Veloria/Source/Assets.xcassets/icon/hot_icon_01.imageset/Frame@3x.png
vendored
Normal file
BIN
Veloria/Source/Assets.xcassets/icon/hot_icon_01.imageset/Frame@3x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
6
Veloria/Source/Assets.xcassets/image/Contents.json
Normal file
6
Veloria/Source/Assets.xcassets/image/Contents.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
22
Veloria/Source/Assets.xcassets/image/bg_image_01.imageset/Contents.json
vendored
Normal file
22
Veloria/Source/Assets.xcassets/image/bg_image_01.imageset/Contents.json
vendored
Normal 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
|
||||
}
|
||||
}
|
BIN
Veloria/Source/Assets.xcassets/image/bg_image_01.imageset/背景@2x.png
vendored
Normal file
BIN
Veloria/Source/Assets.xcassets/image/bg_image_01.imageset/背景@2x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 921 KiB |
BIN
Veloria/Source/Assets.xcassets/image/bg_image_01.imageset/背景@3x.png
vendored
Normal file
BIN
Veloria/Source/Assets.xcassets/image/bg_image_01.imageset/背景@3x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.0 MiB |
@ -3,3 +3,6 @@
|
||||
//
|
||||
|
||||
#import <YYKit/YYKit.h>
|
||||
#import <WMZPageController/WMZPageController.h>
|
||||
#import "JXUUID.h"
|
||||
#import <Toast.h>
|
||||
|
@ -7,3 +7,4 @@
|
||||
*/
|
||||
|
||||
"Home" = "Home";
|
||||
"All" = "All";
|
||||
|
252
Veloria/Thirdparty/JXButton/JXButton.swift
vendored
Normal file
252
Veloria/Thirdparty/JXButton/JXButton.swift
vendored
Normal 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
20
Veloria/Thirdparty/JXUUID/JXUUID.h
vendored
Normal 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
47
Veloria/Thirdparty/JXUUID/JXUUID.m
vendored
Normal 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
31
Veloria/Thirdparty/JXUUID/PDKeyChain.h
vendored
Executable 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
100
Veloria/Thirdparty/JXUUID/PDKeyChain.m
vendored
Executable 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
|
591
Veloria/Thirdparty/ZKCycleScrollView-Swift/ZKCycleScrollView.swift
vendored
Normal file
591
Veloria/Thirdparty/ZKCycleScrollView-Swift/ZKCycleScrollView.swift
vendored
Normal 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))!
|
||||
}
|
||||
}
|
168
Veloria/Thirdparty/ZKCycleScrollView-Swift/ZKCycleScrollViewFlowLayout.swift
vendored
Normal file
168
Veloria/Thirdparty/ZKCycleScrollView-Swift/ZKCycleScrollViewFlowLayout.swift
vendored
Normal 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)
|
||||
/// 计算collectionView最中心点的y值
|
||||
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)
|
||||
/// 计算collectionView最中心点的y值
|
||||
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
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user