对搜索的完善

This commit is contained in:
Mr.zhou 2024-05-20 13:44:14 +08:00
parent 971cad0358
commit 0a5253a349
226 changed files with 12877 additions and 4253 deletions

View File

@ -23,6 +23,12 @@
CB0918A12BD26B0A006D2B39 /* MPPositive_SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0918A02BD26B0A006D2B39 /* MPPositive_SearchViewController.swift */; };
CB0918A32BD26B2F006D2B39 /* MPPositive_LibraryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0918A22BD26B2F006D2B39 /* MPPositive_LibraryViewController.swift */; };
CB0918A52BD26E16006D2B39 /* MPPositive_BottomShowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0918A42BD26E16006D2B39 /* MPPositive_BottomShowView.swift */; };
CB102F502BFAE64C00E967D8 /* MPPositive_RecommendViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB102F4F2BFAE64C00E967D8 /* MPPositive_RecommendViewController.swift */; };
CB102F522BFAE73800E967D8 /* MPPositive_JsonRecommend.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB102F512BFAE73800E967D8 /* MPPositive_JsonRecommend.swift */; };
CB102F552BFAFA7200E967D8 /* MP_CircularProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB102F532BFAFA7200E967D8 /* MP_CircularProgressView.swift */; };
CB102F562BFAFA7200E967D8 /* MP_DownloadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB102F542BFAFA7200E967D8 /* MP_DownloadManager.swift */; };
CB102F582BFAFFCC00E967D8 /* MPPositive_RecommendListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB102F572BFAFFCC00E967D8 /* MPPositive_RecommendListViewModel.swift */; };
CB102F5A2BFB002C00E967D8 /* MPPositive_RecommendLoadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB102F592BFB002C00E967D8 /* MPPositive_RecommendLoadViewModel.swift */; };
CB1C16522BC80BF100B96AB3 /* MPSideA_MediaCenterManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB1C16512BC80BF100B96AB3 /* MPSideA_MediaCenterManager.swift */; };
CB5661292BE09D0500CFD014 /* MPPositive_JsonPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB5661282BE09D0500CFD014 /* MPPositive_JsonPlayer.swift */; };
CB56612D2BE0DF8C00CFD014 /* MP_WebWork.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB56612C2BE0DF8C00CFD014 /* MP_WebWork.swift */; };
@ -132,6 +138,16 @@
CBD313612BD6453A0015D227 /* MPPositive_HomeListFifthCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD313602BD6453A0015D227 /* MPPositive_HomeListFifthCollectionViewCell.swift */; };
CBD5AEE12BBBE45300BF5A43 /* ImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD5AEE02BBBE45300BF5A43 /* ImagePicker.swift */; };
CBD5E80C2BF33D0200A3EBED /* MPPositive_SearchResultTypeShowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD5E80B2BF33D0200A3EBED /* MPPositive_SearchResultTypeShowView.swift */; };
CBD5E80E2BF3501200A3EBED /* MPPositive_JsonSearchTypeResults.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD5E80D2BF3501200A3EBED /* MPPositive_JsonSearchTypeResults.swift */; };
CBD5E8102BF361B900A3EBED /* MPPositive_JsonSearchTypeContinuation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD5E80F2BF361B900A3EBED /* MPPositive_JsonSearchTypeContinuation.swift */; };
CBD6F2122BF4499800343A4A /* MPPositive_ArtistShowViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD6F2112BF4499800343A4A /* MPPositive_ArtistShowViewController.swift */; };
CBD6F2142BF44D8A00343A4A /* MPPositive_JsonArtist.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD6F2132BF44D8A00343A4A /* MPPositive_JsonArtist.swift */; };
CBD6F2162BF48DDD00343A4A /* MPPositive_ArtistHeaderModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD6F2152BF48DDD00343A4A /* MPPositive_ArtistHeaderModel.swift */; };
CBD6F2182BF4A29B00343A4A /* MPPositive_ArtistViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD6F2172BF4A29B00343A4A /* MPPositive_ArtistViewModel.swift */; };
CBD6F21A2BF4A38C00343A4A /* MPPositive_ArtistContentListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD6F2192BF4A38C00343A4A /* MPPositive_ArtistContentListViewModel.swift */; };
CBD6F21C2BF4AEE600343A4A /* MPPositive_ArtistShowHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD6F21B2BF4AEE600343A4A /* MPPositive_ArtistShowHeaderView.swift */; };
CBD6F21E2BF4B61F00343A4A /* MPPositive_ArtistShowTypeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD6F21D2BF4B61F00343A4A /* MPPositive_ArtistShowTypeView.swift */; };
CBD6F2202BF4CE8E00343A4A /* MPPositive_JsonArtistMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD6F21F2BF4CE8E00343A4A /* MPPositive_JsonArtistMore.swift */; };
CBD958D22BB6600500666B0D /* MP_PlayerSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD958D12BB6600500666B0D /* MP_PlayerSlider.swift */; };
CBD958D42BB6942F00666B0D /* MPSideA_VolumeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD958D32BB6942F00666B0D /* MPSideA_VolumeManager.swift */; };
CBDD516D2BEC6AFE000F12C5 /* MPPositive_JsonNext.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBDD516C2BEC6AFE000F12C5 /* MPPositive_JsonNext.swift */; };
@ -148,6 +164,11 @@
CBE2C4C92BC7B25800F283A7 /* TableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBE2C4C82BC7B25800F283A7 /* TableView.swift */; };
CBE2C4CB2BC7BE5D00F283A7 /* MP_NetWorkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBE2C4CA2BC7BE5D00F283A7 /* MP_NetWorkManager.swift */; };
CBE477B12BB16CCC0031C14B /* Macro.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBE477B02BB16CCC0031C14B /* Macro.swift */; };
CBEB017D2BF5D35700D45006 /* MPPositive_ArtistShowSongTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBEB017C2BF5D35700D45006 /* MPPositive_ArtistShowSongTableViewCell.swift */; };
CBEB017F2BF5D49400D45006 /* MPPositive_ArtistShowListableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBEB017E2BF5D49400D45006 /* MPPositive_ArtistShowListableViewCell.swift */; };
CBEB01812BF5D69200D45006 /* MPPositive_ArtistShowListCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBEB01802BF5D69200D45006 /* MPPositive_ArtistShowListCollectionViewCell.swift */; };
CBEB01832BF5D88400D45006 /* MPPositive_ArtistShowCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBEB01822BF5D88400D45006 /* MPPositive_ArtistShowCollectionViewCell.swift */; };
CBEB01852BF5DB3400D45006 /* MPPositive_ArtistDescriptionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBEB01842BF5DB3400D45006 /* MPPositive_ArtistDescriptionTableViewCell.swift */; };
CBEE8E322BEB0FC0007DA798 /* MPPositive_PlayerCoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBEE8E312BEB0FC0007DA798 /* MPPositive_PlayerCoverView.swift */; };
CBEE8E342BEB16BB007DA798 /* MPPositive_PlayerSilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBEE8E332BEB16BB007DA798 /* MPPositive_PlayerSilder.swift */; };
CBEE8E362BEB2604007DA798 /* MPPositive_PlayerLyricView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBEE8E352BEB2604007DA798 /* MPPositive_PlayerLyricView.swift */; };
@ -155,7 +176,7 @@
CBF456DD2BF1E72F00ABF761 /* MPPositive_SearchResultListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBF456DC2BF1E72F00ABF761 /* MPPositive_SearchResultListViewModel.swift */; };
CBF456DF2BF1E8A500ABF761 /* MPPositive_SearchResultItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBF456DE2BF1E8A500ABF761 /* MPPositive_SearchResultItemModel.swift */; };
CBF456E12BF1EB4300ABF761 /* MPPositive_SearchResultItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBF456E02BF1EB4300ABF761 /* MPPositive_SearchResultItemViewModel.swift */; };
CBF456E32BF2086600ABF761 /* MPPositive_LoadSearchResultsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBF456E22BF2086600ABF761 /* MPPositive_LoadSearchResultsViewModel.swift */; };
CBF456E32BF2086600ABF761 /* MPPositive_SearchResultsLoadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBF456E22BF2086600ABF761 /* MPPositive_SearchResultsLoadViewModel.swift */; };
CBF456E72BF20BBD00ABF761 /* MPPositive_SearchResultShowTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBF456E62BF20BBD00ABF761 /* MPPositive_SearchResultShowTableViewCell.swift */; };
CBF456E92BF21E0E00ABF761 /* MPPositive_SearchResultPreviewShowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBF456E82BF21E0E00ABF761 /* MPPositive_SearchResultPreviewShowView.swift */; };
CBF456EB2BF222EC00ABF761 /* MPPositive_SearchSuggestionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBF456EA2BF222EC00ABF761 /* MPPositive_SearchSuggestionsView.swift */; };
@ -187,6 +208,12 @@
CB0918A02BD26B0A006D2B39 /* MPPositive_SearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_SearchViewController.swift; sourceTree = "<group>"; };
CB0918A22BD26B2F006D2B39 /* MPPositive_LibraryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_LibraryViewController.swift; sourceTree = "<group>"; };
CB0918A42BD26E16006D2B39 /* MPPositive_BottomShowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_BottomShowView.swift; sourceTree = "<group>"; };
CB102F4F2BFAE64C00E967D8 /* MPPositive_RecommendViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_RecommendViewController.swift; sourceTree = "<group>"; };
CB102F512BFAE73800E967D8 /* MPPositive_JsonRecommend.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_JsonRecommend.swift; sourceTree = "<group>"; };
CB102F532BFAFA7200E967D8 /* MP_CircularProgressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MP_CircularProgressView.swift; sourceTree = "<group>"; };
CB102F542BFAFA7200E967D8 /* MP_DownloadManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MP_DownloadManager.swift; sourceTree = "<group>"; };
CB102F572BFAFFCC00E967D8 /* MPPositive_RecommendListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_RecommendListViewModel.swift; sourceTree = "<group>"; };
CB102F592BFB002C00E967D8 /* MPPositive_RecommendLoadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_RecommendLoadViewModel.swift; sourceTree = "<group>"; };
CB1C16512BC80BF100B96AB3 /* MPSideA_MediaCenterManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPSideA_MediaCenterManager.swift; sourceTree = "<group>"; };
CB5661282BE09D0500CFD014 /* MPPositive_JsonPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_JsonPlayer.swift; sourceTree = "<group>"; };
CB56612C2BE0DF8C00CFD014 /* MP_WebWork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MP_WebWork.swift; sourceTree = "<group>"; };
@ -296,6 +323,16 @@
CBD313602BD6453A0015D227 /* MPPositive_HomeListFifthCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_HomeListFifthCollectionViewCell.swift; sourceTree = "<group>"; };
CBD5AEE02BBBE45300BF5A43 /* ImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePicker.swift; sourceTree = "<group>"; };
CBD5E80B2BF33D0200A3EBED /* MPPositive_SearchResultTypeShowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_SearchResultTypeShowView.swift; sourceTree = "<group>"; };
CBD5E80D2BF3501200A3EBED /* MPPositive_JsonSearchTypeResults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_JsonSearchTypeResults.swift; sourceTree = "<group>"; };
CBD5E80F2BF361B900A3EBED /* MPPositive_JsonSearchTypeContinuation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_JsonSearchTypeContinuation.swift; sourceTree = "<group>"; };
CBD6F2112BF4499800343A4A /* MPPositive_ArtistShowViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_ArtistShowViewController.swift; sourceTree = "<group>"; };
CBD6F2132BF44D8A00343A4A /* MPPositive_JsonArtist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_JsonArtist.swift; sourceTree = "<group>"; };
CBD6F2152BF48DDD00343A4A /* MPPositive_ArtistHeaderModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_ArtistHeaderModel.swift; sourceTree = "<group>"; };
CBD6F2172BF4A29B00343A4A /* MPPositive_ArtistViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_ArtistViewModel.swift; sourceTree = "<group>"; };
CBD6F2192BF4A38C00343A4A /* MPPositive_ArtistContentListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_ArtistContentListViewModel.swift; sourceTree = "<group>"; };
CBD6F21B2BF4AEE600343A4A /* MPPositive_ArtistShowHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_ArtistShowHeaderView.swift; sourceTree = "<group>"; };
CBD6F21D2BF4B61F00343A4A /* MPPositive_ArtistShowTypeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_ArtistShowTypeView.swift; sourceTree = "<group>"; };
CBD6F21F2BF4CE8E00343A4A /* MPPositive_JsonArtistMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_JsonArtistMore.swift; sourceTree = "<group>"; };
CBD958D12BB6600500666B0D /* MP_PlayerSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MP_PlayerSlider.swift; sourceTree = "<group>"; };
CBD958D32BB6942F00666B0D /* MPSideA_VolumeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPSideA_VolumeManager.swift; sourceTree = "<group>"; };
CBDD516C2BEC6AFE000F12C5 /* MPPositive_JsonNext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_JsonNext.swift; sourceTree = "<group>"; };
@ -312,6 +349,11 @@
CBE2C4C82BC7B25800F283A7 /* TableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableView.swift; sourceTree = "<group>"; };
CBE2C4CA2BC7BE5D00F283A7 /* MP_NetWorkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MP_NetWorkManager.swift; sourceTree = "<group>"; };
CBE477B02BB16CCC0031C14B /* Macro.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Macro.swift; sourceTree = "<group>"; };
CBEB017C2BF5D35700D45006 /* MPPositive_ArtistShowSongTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_ArtistShowSongTableViewCell.swift; sourceTree = "<group>"; };
CBEB017E2BF5D49400D45006 /* MPPositive_ArtistShowListableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_ArtistShowListableViewCell.swift; sourceTree = "<group>"; };
CBEB01802BF5D69200D45006 /* MPPositive_ArtistShowListCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_ArtistShowListCollectionViewCell.swift; sourceTree = "<group>"; };
CBEB01822BF5D88400D45006 /* MPPositive_ArtistShowCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_ArtistShowCollectionViewCell.swift; sourceTree = "<group>"; };
CBEB01842BF5DB3400D45006 /* MPPositive_ArtistDescriptionTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_ArtistDescriptionTableViewCell.swift; sourceTree = "<group>"; };
CBEE8E312BEB0FC0007DA798 /* MPPositive_PlayerCoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_PlayerCoverView.swift; sourceTree = "<group>"; };
CBEE8E332BEB16BB007DA798 /* MPPositive_PlayerSilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_PlayerSilder.swift; sourceTree = "<group>"; };
CBEE8E352BEB2604007DA798 /* MPPositive_PlayerLyricView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_PlayerLyricView.swift; sourceTree = "<group>"; };
@ -319,7 +361,7 @@
CBF456DC2BF1E72F00ABF761 /* MPPositive_SearchResultListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_SearchResultListViewModel.swift; sourceTree = "<group>"; };
CBF456DE2BF1E8A500ABF761 /* MPPositive_SearchResultItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_SearchResultItemModel.swift; sourceTree = "<group>"; };
CBF456E02BF1EB4300ABF761 /* MPPositive_SearchResultItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_SearchResultItemViewModel.swift; sourceTree = "<group>"; };
CBF456E22BF2086600ABF761 /* MPPositive_LoadSearchResultsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_LoadSearchResultsViewModel.swift; sourceTree = "<group>"; };
CBF456E22BF2086600ABF761 /* MPPositive_SearchResultsLoadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_SearchResultsLoadViewModel.swift; sourceTree = "<group>"; };
CBF456E62BF20BBD00ABF761 /* MPPositive_SearchResultShowTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_SearchResultShowTableViewCell.swift; sourceTree = "<group>"; };
CBF456E82BF21E0E00ABF761 /* MPPositive_SearchResultPreviewShowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_SearchResultPreviewShowView.swift; sourceTree = "<group>"; };
CBF456EA2BF222EC00ABF761 /* MPPositive_SearchSuggestionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_SearchSuggestionsView.swift; sourceTree = "<group>"; };
@ -398,6 +440,7 @@
children = (
CBB5D3212BDF80C800CC333D /* MPPositive_PlayerViewController.swift */,
CBCC23502BEE58C1004D7A57 /* MPPositive_PlayerListShowViewController.swift */,
CB102F4F2BFAE64C00E967D8 /* MPPositive_RecommendViewController.swift */,
);
name = "Player播放器";
path = "MusicPlayer/MP/MPPositive/ViewControllers/Player播放器";
@ -674,6 +717,7 @@
children = (
CBC32A522BD8D9F300687171 /* MPPositive_BrowseItemModel.swift */,
CBE1CB4B2BDE440E00701D57 /* MPPositive_ListHeaderModel.swift */,
CBD6F2152BF48DDD00343A4A /* MPPositive_ArtistHeaderModel.swift */,
CBB5D31E2BDF711600CC333D /* MPPositive_SongItemModel.swift */,
CBB75B0A2BEF0BC400B3FF9A /* MPPositive_DownloadItemModel.swift */,
CBFECE382BF0CFF900E07DC4 /* MPPositive_SearchSuggestionItemModel.swift */,
@ -689,7 +733,10 @@
CBD0CC5D2BDA260500C4B64D /* MPPositive_BrowseItemViewModel.swift */,
CBE1CB4D2BDE4BD800701D57 /* MPPositive_ListAlbumListViewModel.swift */,
CBE1CB4F2BDE4CC500701D57 /* MPPositive_ListHeaderViewModel.swift */,
CBD6F2172BF4A29B00343A4A /* MPPositive_ArtistViewModel.swift */,
CBD6F2192BF4A38C00343A4A /* MPPositive_ArtistContentListViewModel.swift */,
CBEE8E372BEB92CC007DA798 /* MPPositive_SongViewModel.swift */,
CB102F572BFAFFCC00E967D8 /* MPPositive_RecommendListViewModel.swift */,
CBE16B942BF251FF005B7EE6 /* MPPositive_SearchSuggestionItemListModel.swift */,
CBF456DC2BF1E72F00ABF761 /* MPPositive_SearchResultListViewModel.swift */,
CBF456E02BF1EB4300ABF761 /* MPPositive_SearchResultItemViewModel.swift */,
@ -702,7 +749,8 @@
children = (
CBD0CC582BDA238100C4B64D /* MPPositive_BrowseLoadViewModel.swift */,
CBDD516E2BECBA6E000F12C5 /* MPPositive_PlayerLoadViewModel.swift */,
CBF456E22BF2086600ABF761 /* MPPositive_LoadSearchResultsViewModel.swift */,
CBF456E22BF2086600ABF761 /* MPPositive_SearchResultsLoadViewModel.swift */,
CB102F592BFB002C00E967D8 /* MPPositive_RecommendLoadViewModel.swift */,
);
path = LoadViewModels;
sourceTree = "<group>";
@ -719,7 +767,7 @@
CBCB4FE52BD11402009760B3 /* Models */ = {
isa = PBXGroup;
children = (
CBD0CC5C2BDA253800C4B64D /* JsonStructs */,
CBD0CC5C2BDA253800C4B64D /* JsonStructs(js文件结构) */,
CBCB4FE42BD11402009760B3 /* ViewModels */,
CBCB4FE12BD11402009760B3 /* Models */,
);
@ -730,7 +778,7 @@
isa = PBXGroup;
children = (
CBCB501D2BD118AA009760B3 /* Base基类导航栏标签栏 */,
CBCB501C2BD118AA009760B3 /* Home首页各项列表页播放器页) */,
CBCB501C2BD118AA009760B3 /* Home首页各项列表页艺术家页) */,
CBB5D3202BDF7DF100CC333D /* Player播放器 */,
CBCB501F2BD118AA009760B3 /* Search搜索页 */,
CBCB501E2BD118AA009760B3 /* Center个人曲库页 */,
@ -760,14 +808,15 @@
path = MPPositive;
sourceTree = "<group>";
};
CBCB501C2BD118AA009760B3 /* Home首页各项列表页播放器页) */ = {
CBCB501C2BD118AA009760B3 /* Home首页各项列表页艺术家页) */ = {
isa = PBXGroup;
children = (
CB09189E2BD26AFC006D2B39 /* MPPositive_HomeViewController.swift */,
CBE1CB432BDDEAAD00701D57 /* MPPositive_MoreContentViewController.swift */,
CBE1CB572BDE550800701D57 /* MPPositive_ListShowViewController.swift */,
CBD6F2112BF4499800343A4A /* MPPositive_ArtistShowViewController.swift */,
);
path = "Home首页各项列表页播放器页)";
path = "Home首页各项列表页艺术家页)";
sourceTree = "<group>";
};
CBCB501D2BD118AA009760B3 /* Base基类导航栏标签栏 */ = {
@ -840,22 +889,34 @@
CBD313602BD6453A0015D227 /* MPPositive_HomeListFifthCollectionViewCell.swift */,
CBE1CB492BDDEBF000701D57 /* MPPositive_MoreListContentCollectionViewCell.swift */,
CBB5D31C2BDF4E9600CC333D /* MPPositive_MusicItemShowTableViewCell.swift */,
CBD6F21B2BF4AEE600343A4A /* MPPositive_ArtistShowHeaderView.swift */,
CBD6F21D2BF4B61F00343A4A /* MPPositive_ArtistShowTypeView.swift */,
CBEB017C2BF5D35700D45006 /* MPPositive_ArtistShowSongTableViewCell.swift */,
CBEB01842BF5DB3400D45006 /* MPPositive_ArtistDescriptionTableViewCell.swift */,
CBEB017E2BF5D49400D45006 /* MPPositive_ArtistShowListableViewCell.swift */,
CBEB01802BF5D69200D45006 /* MPPositive_ArtistShowListCollectionViewCell.swift */,
CBEB01822BF5D88400D45006 /* MPPositive_ArtistShowCollectionViewCell.swift */,
);
path = Home;
sourceTree = "<group>";
};
CBD0CC5C2BDA253800C4B64D /* JsonStructs */ = {
CBD0CC5C2BDA253800C4B64D /* JsonStructs(js文件结构) */ = {
isa = PBXGroup;
children = (
CBCB34DA2BD7ACE900802900 /* MPPositive_JsonBrowse.swift */,
CBE1CB512BDE4F6C00701D57 /* MPPositive_JsonListAlbum.swift */,
CBD6F2132BF44D8A00343A4A /* MPPositive_JsonArtist.swift */,
CBD6F21F2BF4CE8E00343A4A /* MPPositive_JsonArtistMore.swift */,
CBDD516C2BEC6AFE000F12C5 /* MPPositive_JsonNext.swift */,
CB5661282BE09D0500CFD014 /* MPPositive_JsonPlayer.swift */,
CBB9F9DC2BEDCFEE008338DE /* MPPositive_JsonLyrics.swift */,
CB102F512BFAE73800E967D8 /* MPPositive_JsonRecommend.swift */,
CBFECE362BF0C11000E07DC4 /* MPPositive_JsonSearchSuggestions.swift */,
CBFECE3E2BF1176B00E07DC4 /* MPPositive_JsonSearchResults.swift */,
CBD5E80D2BF3501200A3EBED /* MPPositive_JsonSearchTypeResults.swift */,
CBD5E80F2BF361B900A3EBED /* MPPositive_JsonSearchTypeContinuation.swift */,
);
path = JsonStructs;
path = "JsonStructs(js文件结构)";
sourceTree = "<group>";
};
CBE4779F2BB16ABD0031C14B /* MP */ = {
@ -895,6 +956,8 @@
CBE2C4C62BC783F700F283A7 /* MP_HUD.swift */,
CBC687482BC2882B0023ECA6 /* MPTableManager.swift */,
CBD958D12BB6600500666B0D /* MP_PlayerSlider.swift */,
CB102F532BFAFA7200E967D8 /* MP_CircularProgressView.swift */,
CB102F542BFAFA7200E967D8 /* MP_DownloadManager.swift */,
);
path = "Tool(工具封装)";
sourceTree = "<group>";
@ -1094,8 +1157,10 @@
CB0918A52BD26E16006D2B39 /* MPPositive_BottomShowView.swift in Sources */,
CBB5D31F2BDF711600CC333D /* MPPositive_SongItemModel.swift in Sources */,
CBCAFB5A2BB3C2A000BC6520 /* LayoutConstraint.swift in Sources */,
CB102F522BFAE73800E967D8 /* MPPositive_JsonRecommend.swift in Sources */,
CBCB4FEF2BD11402009760B3 /* MPSideA_NavigationController.swift in Sources */,
CBF456DD2BF1E72F00ABF761 /* MPPositive_SearchResultListViewModel.swift in Sources */,
CBEB01832BF5D88400D45006 /* MPPositive_ArtistShowCollectionViewCell.swift in Sources */,
CBD5E80C2BF33D0200A3EBED /* MPPositive_SearchResultTypeShowView.swift in Sources */,
CBCB35212BD7ACE900802900 /* MPPositive_JsonBrowse.swift in Sources */,
CBCB4FF62BD11402009760B3 /* MPSideA_DeleteViewController.swift in Sources */,
@ -1103,6 +1168,7 @@
CBF456DF2BF1E8A500ABF761 /* MPPositive_SearchResultItemModel.swift in Sources */,
CBF456E72BF20BBD00ABF761 /* MPPositive_SearchResultShowTableViewCell.swift in Sources */,
CBDD516F2BECBA6E000F12C5 /* MPPositive_PlayerLoadViewModel.swift in Sources */,
CBD6F2122BF4499800343A4A /* MPPositive_ArtistShowViewController.swift in Sources */,
CBEE8E382BEB92CC007DA798 /* MPPositive_SongViewModel.swift in Sources */,
CBCB50122BD11402009760B3 /* MPSideA_Home_FirstListCollectionViewCell.swift in Sources */,
CB1C16522BC80BF100B96AB3 /* MPSideA_MediaCenterManager.swift in Sources */,
@ -1112,24 +1178,30 @@
CBD313592BD63DDB0015D227 /* MPPositive_HomeListThirdCollectionViewCell.swift in Sources */,
CBD958D42BB6942F00666B0D /* MPSideA_VolumeManager.swift in Sources */,
CBBFA9182BBA83BA00057FD5 /* MP_CoreDataHandlerManager.swift in Sources */,
CBEB01812BF5D69200D45006 /* MPPositive_ArtistShowListCollectionViewCell.swift in Sources */,
CBE1CB442BDDEAAD00701D57 /* MPPositive_MoreContentViewController.swift in Sources */,
CBE1CB522BDE4F6C00701D57 /* MPPositive_JsonListAlbum.swift in Sources */,
CB0918992BD25DCC006D2B39 /* MPPositive_CustomTabBar.swift in Sources */,
CBCB50192BD11402009760B3 /* MPSideA_Home_SecondListCollectionViewCell.swift in Sources */,
CBCB50022BD11402009760B3 /* MPSideA_CountTimerViewController.swift in Sources */,
CBCAFB662BB3C82C00BC6520 /* MP_LunchViewController.swift in Sources */,
CBD6F21E2BF4B61F00343A4A /* MPPositive_ArtistShowTypeView.swift in Sources */,
CBCB4FEA2BD11402009760B3 /* MPSideA_MusicModel.swift in Sources */,
CBBFA91A2BBA846600057FD5 /* CoreDataDelegete.swift in Sources */,
CBF456E32BF2086600ABF761 /* MPPositive_LoadSearchResultsViewModel.swift in Sources */,
CBF456E32BF2086600ABF761 /* MPPositive_SearchResultsLoadViewModel.swift in Sources */,
CB56612D2BE0DF8C00CFD014 /* MP_WebWork.swift in Sources */,
CBFECE352BF0847F00E07DC4 /* MPPositive_SearchSuggestionItemTableViewCell.swift in Sources */,
CB102F562BFAFA7200E967D8 /* MP_DownloadManager.swift in Sources */,
CBCB50042BD11402009760B3 /* MPSideA_HomeViewController.swift in Sources */,
CBD5E80E2BF3501200A3EBED /* MPPositive_JsonSearchTypeResults.swift in Sources */,
CB09189D2BD25F63006D2B39 /* MPPositive_CustomTabBarItem.swift in Sources */,
CBCB4FFC2BD11402009760B3 /* MPSideA_RenameViewController.swift in Sources */,
CBCB50162BD11402009760B3 /* MPSideA_Home_HeadBannerView.swift in Sources */,
CBCC234F2BEE57AC004D7A57 /* MPPositive_PresentationController.swift in Sources */,
CB102F5A2BFB002C00E967D8 /* MPPositive_RecommendLoadViewModel.swift in Sources */,
CBCB500A2BD11402009760B3 /* MPSideA_CustomTabBar.swift in Sources */,
CBCAFB692BB3CAC400BC6520 /* MP_Lunch_ProgressView.swift in Sources */,
CBD6F2182BF4A29B00343A4A /* MPPositive_ArtistViewModel.swift in Sources */,
CBCB50102BD11402009760B3 /* MPSideA_SettingTableViewCell.swift in Sources */,
CBCAFB5D2BB3C52100BC6520 /* HexColor.swift in Sources */,
CB0918A12BD26B0A006D2B39 /* MPPositive_SearchViewController.swift in Sources */,
@ -1138,12 +1210,15 @@
CBD0CC592BDA238100C4B64D /* MPPositive_BrowseLoadViewModel.swift in Sources */,
CBCB50082BD11402009760B3 /* MPSideA_BottomShowView.swift in Sources */,
CBEE8E342BEB16BB007DA798 /* MPPositive_PlayerSilder.swift in Sources */,
CBEB017F2BF5D49400D45006 /* MPPositive_ArtistShowListableViewCell.swift in Sources */,
CB09189F2BD26AFC006D2B39 /* MPPositive_HomeViewController.swift in Sources */,
CBF456ED2BF2253D00ABF761 /* MPPositive_SearchResultsShowView.swift in Sources */,
CBCAFB612BB3C59500BC6520 /* InstanceFromNib.swift in Sources */,
CBCB4FFA2BD11402009760B3 /* MPSideA_PrivacyViewController.swift in Sources */,
CBD6F21C2BF4AEE600343A4A /* MPPositive_ArtistShowHeaderView.swift in Sources */,
CBCB500E2BD11402009760B3 /* MPSideA_CenterTableViewCell.swift in Sources */,
009662312BB14A5A00FCA65F /* ViewController.swift in Sources */,
CBEB01852BF5DB3400D45006 /* MPPositive_ArtistDescriptionTableViewCell.swift in Sources */,
CBE2C4C72BC783F700F283A7 /* MP_HUD.swift in Sources */,
CBE2C4C92BC7B25800F283A7 /* TableView.swift in Sources */,
CBCB4F9A2BD11089009760B3 /* MP_NavigationController.swift in Sources */,
@ -1157,6 +1232,7 @@
0096622D2BB14A5A00FCA65F /* AppDelegate.swift in Sources */,
CBC32A532BD8D9F300687171 /* MPPositive_BrowseItemModel.swift in Sources */,
CBE16B952BF251FF005B7EE6 /* MPPositive_SearchSuggestionItemListModel.swift in Sources */,
CB102F552BFAFA7200E967D8 /* MP_CircularProgressView.swift in Sources */,
CBCB4FEC2BD11402009760B3 /* MPSideA_AddViewController.swift in Sources */,
CBB75B0B2BEF0BC400B3FF9A /* MPPositive_DownloadItemModel.swift in Sources */,
CBCB50172BD11402009760B3 /* MPSideA_Home_RowListsTableViewCell.swift in Sources */,
@ -1164,18 +1240,24 @@
CBD0CC5E2BDA260500C4B64D /* MPPositive_BrowseItemViewModel.swift in Sources */,
CBD313612BD6453A0015D227 /* MPPositive_HomeListFifthCollectionViewCell.swift in Sources */,
CBE1CB4A2BDDEBF000701D57 /* MPPositive_MoreListContentCollectionViewCell.swift in Sources */,
CB102F502BFAE64C00E967D8 /* MPPositive_RecommendViewController.swift in Sources */,
CBCB4FF82BD11402009760B3 /* MPSideA_MoreViewController.swift in Sources */,
CBCB50142BD11402009760B3 /* MPSideA_Home_FourthListCollectionViewCell.swift in Sources */,
CB5661292BE09D0500CFD014 /* MPPositive_JsonPlayer.swift in Sources */,
CBD5E8102BF361B900A3EBED /* MPPositive_JsonSearchTypeContinuation.swift in Sources */,
CBCB50062BD11402009760B3 /* MPSideA_PlayerViewController.swift in Sources */,
CBCB4FE92BD11402009760B3 /* MPSideA_LoadDataMusic.swift in Sources */,
CBCB4FEE2BD11402009760B3 /* MPSideA_BaseViewController.swift in Sources */,
CBE1CB582BDE550800701D57 /* MPPositive_ListShowViewController.swift in Sources */,
CBCB500B2BD11402009760B3 /* MPSideA_CustomTabBarItem.swift in Sources */,
CBB5D31D2BDF4E9600CC333D /* MPPositive_MusicItemShowTableViewCell.swift in Sources */,
CBEB017D2BF5D35700D45006 /* MPPositive_ArtistShowSongTableViewCell.swift in Sources */,
CBCC23512BEE58C1004D7A57 /* MPPositive_PlayerListShowViewController.swift in Sources */,
CBDD516D2BEC6AFE000F12C5 /* MPPositive_JsonNext.swift in Sources */,
CBD6F2202BF4CE8E00343A4A /* MPPositive_JsonArtistMore.swift in Sources */,
CBD6F21A2BF4A38C00343A4A /* MPPositive_ArtistContentListViewModel.swift in Sources */,
CB0918912BD255EA006D2B39 /* MPPositive_NavigationController.swift in Sources */,
CB102F582BFAFFCC00E967D8 /* MPPositive_RecommendListViewModel.swift in Sources */,
CBCB4FF42BD11402009760B3 /* MPSideA_CenterViewController.swift in Sources */,
009662372BB14A5A00FCA65F /* MusicPlayer.xcdatamodeld in Sources */,
CB09189B2BD25F50006D2B39 /* MPPositive_CustomTabBarView.swift in Sources */,
@ -1185,7 +1267,9 @@
CBD958D22BB6600500666B0D /* MP_PlayerSlider.swift in Sources */,
CBCC23532BEE596E004D7A57 /* MPPositive_PlayerListShowTableViewCell.swift in Sources */,
CBC687492BC2882B0023ECA6 /* MPTableManager.swift in Sources */,
CBD6F2142BF44D8A00343A4A /* MPPositive_JsonArtist.swift in Sources */,
CBD313532BD60CD80015D227 /* MPPositive_HomeShowTableViewCell.swift in Sources */,
CBD6F2162BF48DDD00343A4A /* MPPositive_ArtistHeaderModel.swift in Sources */,
CB0918972BD25D8C006D2B39 /* MPPositive_TabBarController.swift in Sources */,
CBCB500C2BD11402009760B3 /* MPSideA_CustomTabBarView.swift in Sources */,
CBB9F9DD2BEDCFEE008338DE /* MPPositive_JsonLyrics.swift in Sources */,

View File

@ -49,13 +49,6 @@
ReferencedContainer = "container:MusicPlayer.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
<AdditionalOption
key = "NSZombieEnabled"
value = "YES"
isEnabled = "YES">
</AdditionalOption>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 731 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 715 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 841 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 B

View File

@ -71,8 +71,6 @@ extension NotificationCenter{
case js_edit_completion
///
case positive_browses_reload
///
case positive_list_reload
///
case switch_player_status
///

View File

@ -38,8 +38,7 @@ let Phone_Model = UIDevice.current.model
let System_Version = UIDevice.current.systemVersion
///
let Language_first_local = NSLocale.preferredLanguages.first!
///
let Location_First = MP_LocationManager.shared.requestLocation()
///
let bottomPadding = UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0
///

View File

@ -0,0 +1,55 @@
//
// MP_CircularProgressView.swift
// MusicPlayer
//
// Created by 16 on 2024/5/14.
//
import Foundation
import UIKit
class CircularProgressView: UIView {
private var progressLayer = CAShapeLayer()
private var trackLayer = CAShapeLayer()
private var progress: CGFloat = 0 {
didSet {
progressLayer.strokeEnd = progress
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupLayers()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupLayers()
}
private func setupLayers() {
// Track layer (background circle)
let center = CGPoint(x: bounds.width / 2, y: bounds.height / 2)
let circularPath = UIBezierPath(arcCenter: center, radius: bounds.width / 2, startAngle: -CGFloat.pi / 2, endAngle: 3 * CGFloat.pi / 2, clockwise: true)
trackLayer.path = circularPath.cgPath
trackLayer.strokeColor = UIColor.lightGray.cgColor
trackLayer.lineWidth = 3
trackLayer.fillColor = UIColor.clear.cgColor
layer.addSublayer(trackLayer)
// Progress layer (foreground circle)
progressLayer.path = circularPath.cgPath
progressLayer.strokeColor = UIColor.green.cgColor
progressLayer.lineWidth = 3
progressLayer.fillColor = UIColor.clear.cgColor
progressLayer.lineCap = .round
progressLayer.strokeEnd = 0
layer.addSublayer(progressLayer)
}
func setProgress(to progress: CGFloat) {
self.progress = progress
}
}

View File

@ -0,0 +1,47 @@
//
// MP_DownloadManager.swift
// MusicPlayer
//
// Created by 16 on 2024/5/14.
//
import Foundation
import Foundation
import Alamofire
class DownloadManager {
static let shared = DownloadManager()
private init() {}
func downloadVideo(from url: URL, videoId: String, progressView: CircularProgressView, completion: @escaping (Result<URL, Error>) -> Void) {
let destination: DownloadRequest.Destination = { _, _ in
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let downloadsURL = documentsURL.appendingPathComponent("Downloads")
// Downloads
if !FileManager.default.fileExists(atPath: downloadsURL.path) {
do {
try FileManager.default.createDirectory(at: downloadsURL, withIntermediateDirectories: true, attributes: nil)
} catch {
completion(.failure(error))
return (downloadsURL, [.removePreviousFile, .createIntermediateDirectories])
}
}
let fileURL = downloadsURL.appendingPathComponent("\(videoId).mp4")
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
AF.download(url, to: destination).downloadProgress { progress in
progressView.setProgress(to: CGFloat(progress.fractionCompleted))
}.response { response in
if let error = response.error {
completion(.failure(error))
} else if let filePath = response.fileURL {
completion(.success(filePath))
}
}
}
}

View File

@ -42,6 +42,8 @@ typealias MP_PlayTimerResumeAction = () -> Void
typealias MP_PlayTimerStopAction = () -> Void
///
typealias MP_PlayTimerEditEndAction = () -> Void
///
typealias MP_PlayCacheValueAction = (_ currentValue:TimeInterval, _ duration:TimeInterval) -> Void
///
class MP_PlayerManager:NSObject{
///
@ -57,6 +59,7 @@ class MP_PlayerManager:NSObject{
}else {
//load
NotificationCenter.notificationKey.post(notificationName: .player_delete_list)
playState = .Null
}
}
}
@ -94,7 +97,8 @@ class MP_PlayerManager:NSObject{
private var startActionBlock:MP_PlayTimerStartAction!
///
var runActionBlock:MP_PlayTimerRunAction!
///
var cacheValueBlock:MP_PlayCacheValueAction!
private override init() {
super.init()
//
@ -151,26 +155,13 @@ class MP_PlayerManager:NSObject{
}
}
})
//Video
if loadPlayer.currentVideo.isPreloading == true {
//
print("开始播放音乐-\(loadPlayer.currentVideo.title ?? "")")
player.play()
playState = .Playing
//
if startActionBlock != nil {
startActionBlock!()
}
}else {
//KVO
//currentVideoresourcePlayerItemKVO,itemstatusplaybackLikelyToKeepUp
loadPlayer.currentVideo.resourcePlayerItem.addObserver(self, forKeyPath: "status", options: .new, context: nil)
loadPlayer.currentVideo.resourcePlayerItem.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: .new, context: nil)
}
//VideoItem
// for item in loadPlayer.listViewVideos where item.song.videoId != loadPlayer.currentVideo.song.videoId {
// item.preloadPlayerItem()
// }
//PlayerItem
//
loadPlayer.currentVideo?.resourcePlayerItem?.addObserver(self, forKeyPath: "status", options: [.old,.new], context: nil)
//
loadPlayer.currentVideo?.resourcePlayerItem.addObserver(self, forKeyPath: "loadedTimeRanges", options: [.old,.new], context: nil)
//PlayerItem
}
//KVO
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
@ -181,30 +172,48 @@ class MP_PlayerManager:NSObject{
switch keyPath {
case "status"://playerItem
if let statuValue = change?[.newKey] as? Int, statuValue == AVPlayerItem.Status.readyToPlay.rawValue {
//statuVlaueplayerItem
print("当前音乐-\(loadPlayer.currentVideo.title ?? "") 已经准备好播放")
}else {
print("当前音乐-\(loadPlayer.currentVideo.title ?? "") 未做好准备播放")
//
}
case "playbackLikelyToKeepUp"://
if let playbackLikelyToKeepUp = change?[.newKey] as? Bool, playbackLikelyToKeepUp == true {
//
print("当前音乐-\(loadPlayer.currentVideo.title ?? "") 有足够的缓存来播放")
//
if playState != .Playing {
//statuVlaueplayerItem
print("当前音乐-\(loadPlayer.currentVideo?.title ?? "") 已经准备好播放")
//
print("开始播放音乐-\(loadPlayer.currentVideo.title ?? "")")
print("开始播放音乐-\(loadPlayer.currentVideo?.title ?? "")")
player.play()
playState = .Playing
//
if startActionBlock != nil {
startActionBlock!()
}
}
}else {
//
print("当前音乐-\(loadPlayer.currentVideo?.title ?? "") 未做好准备播放,失败原因是\(loadPlayer.currentVideo?.resourcePlayerItem.error?.localizedDescription ?? "")")
//
loadPlayer.remakeImproveData {
[weak self] in
guard let self = self else {return}
//
play()
}
}
case "loadedTimeRanges"://
//Item
if let timeRanges = loadPlayer.currentVideo?.resourcePlayerItem.loadedTimeRanges.map({$0.timeRangeValue}), let first = timeRanges.first {
//
let startSeconds = first.start.seconds
//
let durationSeconds = first.duration.seconds
//
let bufferedSeconds = startSeconds + durationSeconds
//
let maxDuration = getMusicDuration()
//
if cacheValueBlock != nil {
cacheValueBlock!(bufferedSeconds, maxDuration)
}
}
case "playbackLikelyToKeepUp"://
break
default:
break
}
@ -224,9 +233,9 @@ class MP_PlayerManager:NSObject{
}
switch playType {
case .single:
playState = .Null
//
player.seek(to: CMTime.zero)
player.play()
default:
//
nextEvent()
@ -399,8 +408,10 @@ class MP_PlayerManager:NSObject{
playState = .Null
//
if let video = sender.object as? MPPositive_SongViewModel {
//KVO
video.resourcePlayerItem.removeObserver(self, forKeyPath: "status")
video.resourcePlayerItem.removeObserver(self, forKeyPath: "playbackLikelyToKeepUp")
video.resourcePlayerItem.removeObserver(self, forKeyPath: "loadedTimeRanges")
// video.resourcePlayerItem.removeObserver(self, forKeyPath: "playbackLikelyToKeepUp")
}
if loadPlayer.currentVideo != nil {
//

View File

@ -0,0 +1,836 @@
//
// MPPositive_JsonArtist.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/15.
//
import UIKit
///
struct JsonArtist: Codable {
///
let header:Header?
///
let contents:Contents?
enum CodingKeys: String, CodingKey {
case header = "header"
case contents = "contents"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
header = try values.decodeIfPresent(Header.self, forKey: .header)
contents = try values.decodeIfPresent(Contents.self, forKey: .contents)
}
struct Header: Codable {
let musicImmersiveHeaderRenderer:MusicImmersiveHeaderRenderer?
enum CodingKeys: String, CodingKey {
case musicImmersiveHeaderRenderer = "musicImmersiveHeaderRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicImmersiveHeaderRenderer = try values.decodeIfPresent(MusicImmersiveHeaderRenderer.self, forKey: .musicImmersiveHeaderRenderer)
}
struct MusicImmersiveHeaderRenderer: Codable {
///
let title:Title?
///
let subscriptionButton:SubscriptionButton?
///
let description:Description?
///
let thumbnail:Thumbnail?
enum CodingKeys: String, CodingKey {
case title = "title"
case subscriptionButton = "subscriptionButton"
case description = "description"
case thumbnail = "thumbnail"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
title = try values.decodeIfPresent(Title.self, forKey: .title)
subscriptionButton = try values.decodeIfPresent(SubscriptionButton.self, forKey: .subscriptionButton)
description = try values.decodeIfPresent(Description.self, forKey: .description)
thumbnail = try values.decodeIfPresent(Thumbnail.self, forKey: .thumbnail)
}
struct Title: Codable {
let runs:[Run]?
enum CodingKeys: String, CodingKey {
case runs = "runs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
runs = try values.decodeIfPresent([Run].self, forKey: .runs)
}
struct Run: Codable {
let text:String?
enum CodingKeys: String, CodingKey {
case text = "text"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
text = try values.decodeIfPresent(String.self, forKey: .text)
}
}
}
struct SubscriptionButton: Codable {
let subscribeButtonRenderer:SubscribeButtonRenderer?
enum CodingKeys: String, CodingKey {
case subscribeButtonRenderer = "subscribeButtonRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
subscribeButtonRenderer = try values.decodeIfPresent(SubscribeButtonRenderer.self, forKey: .subscribeButtonRenderer)
}
struct SubscribeButtonRenderer: Codable {
///
let subscriberCountText:SubscriberCountText?
///
let subscribedButtonText:SubscribedButtonText?
enum CodingKeys: String, CodingKey {
case subscriberCountText = "subscriberCountText"
case subscribedButtonText = "subscribedButtonText"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
subscriberCountText = try values.decodeIfPresent(SubscriberCountText.self, forKey: .subscriberCountText)
subscribedButtonText = try values.decodeIfPresent(SubscribedButtonText.self, forKey: .subscribedButtonText)
}
struct SubscriberCountText: Codable {
let runs:[Run]?
enum CodingKeys: String, CodingKey {
case runs = "runs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
runs = try values.decodeIfPresent([Run].self, forKey: .runs)
}
struct Run: Codable {
let text:String?
enum CodingKeys: String, CodingKey {
case text = "text"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
text = try values.decodeIfPresent(String.self, forKey: .text)
}
}
}
struct SubscribedButtonText: Codable {
let runs:[Run]?
enum CodingKeys: String, CodingKey {
case runs = "runs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
runs = try values.decodeIfPresent([Run].self, forKey: .runs)
}
struct Run: Codable {
let text:String?
enum CodingKeys: String, CodingKey {
case text = "text"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
text = try values.decodeIfPresent(String.self, forKey: .text)
}
}
}
}
}
struct Description:Codable {
let runs:[Run]?
enum CodingKeys: String, CodingKey {
case runs = "runs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
runs = try values.decodeIfPresent([Run].self, forKey: .runs)
}
struct Run: Codable {
let text:String?
enum CodingKeys: String, CodingKey {
case text = "text"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
text = try values.decodeIfPresent(String.self, forKey: .text)
}
}
}
struct Thumbnail: Codable {
let musicThumbnailRenderer:MusicThumbnailRenderer?
enum CodingKeys: String, CodingKey {
case musicThumbnailRenderer = "musicThumbnailRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicThumbnailRenderer = try values.decodeIfPresent(MusicThumbnailRenderer.self, forKey: .musicThumbnailRenderer)
}
struct MusicThumbnailRenderer: Codable {
let thumbnail:Thumbnail?
enum CodingKeys: String, CodingKey {
case thumbnail = "thumbnail"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
thumbnail = try values.decodeIfPresent(Thumbnail.self, forKey: .thumbnail)
}
struct Thumbnail: Codable {
let thumbnails:[Thumbnails]?
enum CodingKeys: String, CodingKey {
case thumbnails = "thumbnails"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
thumbnails = try values.decodeIfPresent([Thumbnails].self, forKey: .thumbnails)
}
struct Thumbnails: Codable {
let url:String?
enum CodingKeys: String, CodingKey {
case url = "url"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
url = try values.decodeIfPresent(String.self, forKey: .url)
}
}
}
}
}
}
}
struct Contents: Codable {
let singleColumnBrowseResultsRenderer:SingleColumnBrowseResultsRenderer?
enum CodingKeys: String, CodingKey {
case singleColumnBrowseResultsRenderer = "singleColumnBrowseResultsRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
singleColumnBrowseResultsRenderer = try values.decodeIfPresent(SingleColumnBrowseResultsRenderer.self, forKey: .singleColumnBrowseResultsRenderer)
}
struct SingleColumnBrowseResultsRenderer: Codable {
let tabs:[Tab]?
enum CodingKeys: String, CodingKey {
case tabs = "tabs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
tabs = try values.decodeIfPresent([Tab].self, forKey: .tabs)
}
struct Tab: Codable {
let tabRenderer:TabRenderer?
enum CodingKeys: String, CodingKey {
case tabRenderer = "tabRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
tabRenderer = try values.decodeIfPresent(TabRenderer.self, forKey: .tabRenderer)
}
struct TabRenderer: Codable {
let content:Content?
enum CodingKeys: String, CodingKey {
case content = "content"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
content = try values.decodeIfPresent(Content.self, forKey: .content)
}
struct Content: Codable {
let sectionListRenderer:SectionListRenderer?
enum CodingKeys: String, CodingKey {
case sectionListRenderer = "sectionListRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
sectionListRenderer = try values.decodeIfPresent(SectionListRenderer.self, forKey: .sectionListRenderer)
}
struct SectionListRenderer: Codable {
///
let contents:[Content]?
enum CodingKeys: String, CodingKey {
case contents = "contents"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
contents = try values.decodeIfPresent([Content].self, forKey: .contents)
}
struct Content: Codable {
///
let musicShelfRenderer:MusicShelfRenderer?
///
let musicCarouselShelfRenderer:MusicCarouselShelfRenderer?
enum CodingKeys: String, CodingKey {
case musicShelfRenderer = "musicShelfRenderer"
case musicCarouselShelfRenderer = "musicCarouselShelfRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicShelfRenderer = try values.decodeIfPresent(MusicShelfRenderer.self, forKey: .musicShelfRenderer)
musicCarouselShelfRenderer = try values.decodeIfPresent(MusicCarouselShelfRenderer.self, forKey: .musicCarouselShelfRenderer)
}
struct MusicShelfRenderer: Codable {
///
let title:Title?
///
let contents:[Content]?
enum CodingKeys: String, CodingKey {
case title = "title"
case contents = "contents"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
title = try values.decodeIfPresent(Title.self, forKey: .title)
contents = try values.decodeIfPresent([Content].self, forKey: .contents)
}
struct Title: Codable {
let runs:[Run]?
enum CodingKeys: String, CodingKey {
case runs = "runs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
runs = try values.decodeIfPresent([Run].self, forKey: .runs)
}
struct Run: Codable {
///
let text:String?
///
let navigationEndpoint:NavigationEndpoint?
enum CodingKeys: String, CodingKey {
case text = "text"
case navigationEndpoint = "navigationEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
text = try values.decodeIfPresent(String.self, forKey: .text)
navigationEndpoint = try values.decodeIfPresent(NavigationEndpoint.self, forKey: .navigationEndpoint)
}
struct NavigationEndpoint: Codable {
let browseEndpoint:BrowseEndpoint?
enum CodingKeys: String, CodingKey {
case browseEndpoint = "browseEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
browseEndpoint = try values.decodeIfPresent(BrowseEndpoint.self, forKey: .browseEndpoint)
}
struct BrowseEndpoint: Codable {
///Id
let browseId: String?
///
let params: String?
enum CodingKeys: String, CodingKey {
case browseId = "browseId"
case params = "params"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
browseId = try values.decodeIfPresent(String.self, forKey: .browseId)
params = try values.decodeIfPresent(String.self, forKey: .params)
}
}
}
}
}
struct Content: Codable {
///
let musicResponsiveListItemRenderer:RootMusicResponsiveListItemRenderer?
enum CodingKeys: String, CodingKey {
case musicResponsiveListItemRenderer = "musicResponsiveListItemRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicResponsiveListItemRenderer = try values.decodeIfPresent(RootMusicResponsiveListItemRenderer.self, forKey: .musicResponsiveListItemRenderer)
}
// struct MusicResponsiveListItemRenderer: Codable {
// ///
// let thumbnail:Thumbnail?
// ///(0)
// let flexColumns:[FlexColumn]?
// ////IDplaylistItemData/
// let playlistItemData:PlaylistItemData?
// ////IDnavigationEndpoint/
// let navigationEndpoint:NavigationEndpoint?
// enum CodingKeys: String, CodingKey {
// case thumbnail = "thumbnail"
// case flexColumns = "flexColumns"
// case playlistItemData = "playlistItemData"
// case navigationEndpoint = "navigationEndpoint"
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// thumbnail = try values.decodeIfPresent(Thumbnail.self, forKey: .thumbnail)
// flexColumns = try values.decodeIfPresent([FlexColumn].self, forKey: .flexColumns)
// playlistItemData = try values.decodeIfPresent(PlaylistItemData.self, forKey: .playlistItemData)
// navigationEndpoint = try values.decodeIfPresent(NavigationEndpoint.self, forKey: .navigationEndpoint)
// }
// struct Thumbnail: Codable {
// let musicThumbnailRenderer:MusicThumbnailRenderer?
// enum CodingKeys: String, CodingKey {
// case musicThumbnailRenderer = "musicThumbnailRenderer"
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// musicThumbnailRenderer = try values.decodeIfPresent(MusicThumbnailRenderer.self, forKey: .musicThumbnailRenderer)
// }
// struct MusicThumbnailRenderer: Codable {
// let thumbnail:Thumbnail?
// enum CodingKeys: String, CodingKey {
// case thumbnail = "thumbnail"
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// thumbnail = try values.decodeIfPresent(Thumbnail.self, forKey: .thumbnail)
// }
// struct Thumbnail: Codable {
// let thumbnails:[Thumbnails]?
// enum CodingKeys: String, CodingKey {
// case thumbnails = "thumbnails"
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// thumbnails = try values.decodeIfPresent([Thumbnails].self, forKey: .thumbnails)
// }
// struct Thumbnails: Codable {
// let url:String?
// enum CodingKeys: String, CodingKey {
// case url = "url"
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// url = try values.decodeIfPresent(String.self, forKey: .url)
// }
// }
// }
// }
// }
// struct FlexColumn: Codable {
// let musicResponsiveListItemFlexColumnRenderer:MusicResponsiveListItemFlexColumnRenderer?
// enum CodingKeys: String, CodingKey {
// case musicResponsiveListItemFlexColumnRenderer = "musicResponsiveListItemFlexColumnRenderer"
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// musicResponsiveListItemFlexColumnRenderer = try values.decodeIfPresent(MusicResponsiveListItemFlexColumnRenderer.self, forKey: .musicResponsiveListItemFlexColumnRenderer)
// }
// struct MusicResponsiveListItemFlexColumnRenderer: Codable {
// let text:Text?
// enum CodingKeys: String, CodingKey {
// case text = "text"
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// text = try values.decodeIfPresent(Text.self, forKey: .text)
// }
// struct Text: Codable {
// let runs:[Run]?
// enum CodingKeys: String, CodingKey {
// case runs = "runs"
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// runs = try values.decodeIfPresent([Run].self, forKey: .runs)
// }
// struct Run: Codable {
// ///
// let text:String?
// let navigationEndpoint:NavigationEndpoint?
// enum CodingKeys: String, CodingKey {
// case text = "text"
// case navigationEndpoint = "navigationEndpoint"
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// text = try values.decodeIfPresent(String.self, forKey: .text)
// navigationEndpoint = try values.decodeIfPresent(NavigationEndpoint.self, forKey: .navigationEndpoint)
// }
// struct NavigationEndpoint: Codable {
// let watchEndpoint:WatchEndpoint?
// enum CodingKeys: String, CodingKey {
// case watchEndpoint = "watchEndpoint"
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// watchEndpoint = try values.decodeIfPresent(WatchEndpoint.self, forKey: .watchEndpoint)
// }
// struct WatchEndpoint: Codable {
// let playlistId:String?
// enum CodingKeys: String, CodingKey {
// case playlistId = "playlistId"
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// playlistId = try values.decodeIfPresent(String.self, forKey: .playlistId)
// }
// }
// }
// }
// }
// }
// }
// struct PlaylistItemData: Codable {
// let videoId:String?
// enum CodingKeys: String, CodingKey {
// case videoId = "videoId"
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// videoId = try values.decodeIfPresent(String.self, forKey: .videoId)
// }
// }
// struct NavigationEndpoint: Codable {
// let browseEndpoint:BrowseEndpoint?
// enum CodingKeys: String, CodingKey {
// case browseEndpoint = "browseEndpoint"
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// browseEndpoint = try values.decodeIfPresent(BrowseEndpoint.self, forKey: .browseEndpoint)
// }
// struct BrowseEndpoint: Codable {
// let browseId:String?
// let browseEndpointContextSupportedConfigs:BrowseEndpointContextSupportedConfigs?
// enum CodingKeys: String, CodingKey {
// case browseId = "browseId"
// case browseEndpointContextSupportedConfigs = "browseEndpointContextSupportedConfigs"
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// browseId = try values.decodeIfPresent(String.self, forKey: .browseId)
// browseEndpointContextSupportedConfigs = try values.decodeIfPresent(BrowseEndpointContextSupportedConfigs.self, forKey: .browseEndpointContextSupportedConfigs)
// }
// struct BrowseEndpointContextSupportedConfigs: Codable {
// let browseEndpointContextMusicConfig:BrowseEndpointContextMusicConfig?
// enum CodingKeys: String, CodingKey {
// case browseEndpointContextMusicConfig = "browseEndpointContextMusicConfig"
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// browseEndpointContextMusicConfig = try values.decodeIfPresent(BrowseEndpointContextMusicConfig.self, forKey: .browseEndpointContextMusicConfig)
// }
// struct BrowseEndpointContextMusicConfig: Codable {
// let pageType:String?
// enum CodingKeys: String, CodingKey {
// case pageType = "pageType"
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// pageType = try values.decodeIfPresent(String.self, forKey: .pageType)
// }
// }
// }
// }
// }
// }
}
}
struct MusicCarouselShelfRenderer: Codable {
///
let header:Header?
///
let contents:[Content]?
enum CodingKeys: String, CodingKey {
case header = "header"
case contents = "contents"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
header = try values.decodeIfPresent(Header.self, forKey: .header)
contents = try values.decodeIfPresent([Content].self, forKey: .contents)
}
struct Header: Codable {
let musicCarouselShelfBasicHeaderRenderer:MusicCarouselShelfBasicHeaderRenderer?
enum CodingKeys: String, CodingKey {
case musicCarouselShelfBasicHeaderRenderer = "musicCarouselShelfBasicHeaderRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicCarouselShelfBasicHeaderRenderer = try values.decodeIfPresent(MusicCarouselShelfBasicHeaderRenderer.self, forKey: .musicCarouselShelfBasicHeaderRenderer)
}
struct MusicCarouselShelfBasicHeaderRenderer: Codable {
let title:Title?
enum CodingKeys: String, CodingKey {
case title = "title"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
title = try values.decodeIfPresent(Title.self, forKey: .title)
}
struct Title: Codable {
let runs:[Run]?
enum CodingKeys: String, CodingKey {
case runs = "runs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
runs = try values.decodeIfPresent([Run].self, forKey: .runs)
}
struct Run: Codable {
///
let text:String?
///
let navigationEndpoint:NavigationEndpoint?
enum CodingKeys: String, CodingKey {
case text = "text"
case navigationEndpoint = "navigationEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
text = try values.decodeIfPresent(String.self, forKey: .text)
navigationEndpoint = try values.decodeIfPresent(NavigationEndpoint.self, forKey: .navigationEndpoint)
}
struct NavigationEndpoint: Codable {
let browseEndpoint:BrowseEndpoint?
enum CodingKeys: String, CodingKey {
case browseEndpoint = "browseEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
browseEndpoint = try values.decodeIfPresent(BrowseEndpoint.self, forKey: .browseEndpoint)
}
struct BrowseEndpoint: Codable {
///Id
let browseId: String?
///
let params: String?
enum CodingKeys: String, CodingKey {
case browseId = "browseId"
case params = "params"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
browseId = try values.decodeIfPresent(String.self, forKey: .browseId)
params = try values.decodeIfPresent(String.self, forKey: .params)
}
}
}
}
}
}
}
struct Content: Codable {
let musicTwoRowItemRenderer:RootMusicTwoRowItemRenderer?
enum CodingKeys: String, CodingKey {
case musicTwoRowItemRenderer = "musicTwoRowItemRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicTwoRowItemRenderer = try values.decodeIfPresent(RootMusicTwoRowItemRenderer.self, forKey: .musicTwoRowItemRenderer)
}
// struct MusicTwoRowItemRenderer: Codable {
// ///
// let thumbnailRenderer:ThumbnailRenderer?
// ///
// let title:Title?
// ///
// let subtitle:Subtitle?
// ///iD
// let navigationEndpoint:NavigationEndpoint?
// enum CodingKeys: String, CodingKey {
// case thumbnailRenderer = "thumbnailRenderer"
// case title = "title"
// case subtitle = "subtitle"
// case navigationEndpoint = "navigationEndpoint"
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// thumbnailRenderer = try values.decodeIfPresent(ThumbnailRenderer.self, forKey: .thumbnailRenderer)
// title = try values.decodeIfPresent(Title.self, forKey: .title)
// subtitle = try values.decodeIfPresent(Subtitle.self, forKey: .subtitle)
// navigationEndpoint = try values.decodeIfPresent(NavigationEndpoint.self, forKey: .navigationEndpoint)
// }
// struct ThumbnailRenderer: Codable {
// let musicThumbnailRenderer:MusicThumbnailRenderer?
// enum CodingKeys: String, CodingKey {
// case musicThumbnailRenderer = "musicThumbnailRenderer"
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// musicThumbnailRenderer = try values.decodeIfPresent(MusicThumbnailRenderer.self, forKey: .musicThumbnailRenderer)
// }
// struct MusicThumbnailRenderer: Codable {
// let thumbnail:Thumbnail?
// enum CodingKeys: String, CodingKey {
// case thumbnail = "thumbnail"
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// thumbnail = try values.decodeIfPresent(Thumbnail.self, forKey: .thumbnail)
// }
// struct Thumbnail: Codable {
// let thumbnails:[Thumbnails]?
// enum CodingKeys: String, CodingKey {
// case thumbnails = "thumbnails"
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// thumbnails = try values.decodeIfPresent([Thumbnails].self, forKey: .thumbnails)
// }
// struct Thumbnails: Codable {
// let url:String?
// enum CodingKeys: String, CodingKey {
// case url = "url"
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// url = try values.decodeIfPresent(String.self, forKey: .url)
// }
// }
// }
// }
// }
// struct Title: Codable {
// let runs:[Run]?
// enum CodingKeys: String, CodingKey {
// case runs = "runs"
//
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// runs = try values.decodeIfPresent([Run].self, forKey: .runs)
// }
// struct Run: Codable {
// let text:String?
// enum CodingKeys: String, CodingKey {
// case text = "text"
//
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// text = try values.decodeIfPresent(String.self, forKey: .text)
// }
// }
// }
// struct Subtitle: Codable {
// let runs:[Run]?
// enum CodingKeys: String, CodingKey {
// case runs = "runs"
//
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// runs = try values.decodeIfPresent([Run].self, forKey: .runs)
// }
// struct Run: Codable {
// let text:String?
// enum CodingKeys: String, CodingKey {
// case text = "text"
//
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// text = try values.decodeIfPresent(String.self, forKey: .text)
// }
// }
// }
// struct NavigationEndpoint: Codable {
// ////
// let browseEndpoint:BrowseEndpoint?
// ///
// let watchEndpoint:WatchEndpoint?
// enum CodingKeys: String, CodingKey {
// case browseEndpoint = "browseEndpoint"
// case watchEndpoint = "watchEndpoint"
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// browseEndpoint = try values.decodeIfPresent(BrowseEndpoint.self, forKey: .browseEndpoint)
// watchEndpoint = try values.decodeIfPresent(WatchEndpoint.self, forKey: .watchEndpoint)
// }
// struct BrowseEndpoint: Codable {
// let browseId:String?
// let params:String?
// let browseEndpointContextSupportedConfigs:BrowseEndpointContextSupportedConfigs?
// enum CodingKeys: String, CodingKey {
// case browseId = "browseId"
// case params = "params"
// case browseEndpointContextSupportedConfigs = "browseEndpointContextSupportedConfigs"
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// browseId = try values.decodeIfPresent(String.self, forKey: .browseId)
// params = try values.decodeIfPresent(String.self, forKey: .params)
// browseEndpointContextSupportedConfigs = try values.decodeIfPresent(BrowseEndpointContextSupportedConfigs.self, forKey: .browseEndpointContextSupportedConfigs)
// }
// struct BrowseEndpointContextSupportedConfigs: Codable {
// let browseEndpointContextMusicConfig:BrowseEndpointContextMusicConfig?
// enum CodingKeys: String, CodingKey {
// case browseEndpointContextMusicConfig = "browseEndpointContextMusicConfig"
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// browseEndpointContextMusicConfig = try values.decodeIfPresent(BrowseEndpointContextMusicConfig.self, forKey: .browseEndpointContextMusicConfig)
// }
// struct BrowseEndpointContextMusicConfig: Codable {
// let pageType:String?
// enum CodingKeys: String, CodingKey {
// case pageType = "pageType"
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// pageType = try values.decodeIfPresent(String.self, forKey: .pageType)
// }
// }
// }
// }
// struct WatchEndpoint: Codable {
// ///ID
// let videoId:String?
// ///ID
// let playlistId:String?
// ///
// let watchEndpointMusicSupportedConfigs:WatchEndpointMusicSupportedConfigs?
// enum CodingKeys: String, CodingKey {
// case videoId = "videoId"
// case playlistId = "playlistId"
// case watchEndpointMusicSupportedConfigs = "watchEndpointMusicSupportedConfigs"
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// videoId = try values.decodeIfPresent(String.self, forKey: .videoId)
// playlistId = try values.decodeIfPresent(String.self, forKey: .playlistId)
// watchEndpointMusicSupportedConfigs = try values.decodeIfPresent(WatchEndpointMusicSupportedConfigs.self, forKey: .watchEndpointMusicSupportedConfigs)
// }
// struct WatchEndpointMusicSupportedConfigs: Codable {
// let watchEndpointMusicConfig:WatchEndpointMusicConfig?
// enum CodingKeys: String, CodingKey {
// case watchEndpointMusicConfig = "watchEndpointMusicConfig"
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// watchEndpointMusicConfig = try values.decodeIfPresent(WatchEndpointMusicConfig.self, forKey: .watchEndpointMusicConfig)
// }
// struct WatchEndpointMusicConfig: Codable {
// let musicVideoType:String?
// enum CodingKeys: String, CodingKey {
// case musicVideoType = "musicVideoType"
// }
// init(from decoder: Decoder) throws {
// let values = try decoder.container(keyedBy: CodingKeys.self)
// musicVideoType = try values.decodeIfPresent(String.self, forKey: .musicVideoType)
// }
// }
// }
// }
// }
// }
}
}
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,274 @@
//
// MPPositive_JsonArtistMore.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/15.
//
import UIKit
struct JsonArtistMore: Codable {
///
let contents:Contents?
///continuation
let continuationContents:ContinuationContents?
enum CodingKeys: String, CodingKey {
case contents = "contents"
case continuationContents = "continuationContents"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
contents = try values.decodeIfPresent(Contents.self, forKey: .contents)
continuationContents = try values.decodeIfPresent(ContinuationContents.self, forKey: .continuationContents)
}
struct Contents: Codable {
let singleColumnBrowseResultsRenderer:SingleColumnBrowseResultsRenderer?
enum CodingKeys: String, CodingKey {
case singleColumnBrowseResultsRenderer = "singleColumnBrowseResultsRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
singleColumnBrowseResultsRenderer = try values.decodeIfPresent(SingleColumnBrowseResultsRenderer.self, forKey: .singleColumnBrowseResultsRenderer)
}
struct SingleColumnBrowseResultsRenderer: Codable {
///1
let tabs : [Tab]?
enum CodingKeys: String, CodingKey {
case tabs = "tabs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
tabs = try values.decodeIfPresent([Tab].self, forKey: .tabs)
}
struct Tab : Codable {
let tabRenderer : TabRenderer?
enum CodingKeys: String, CodingKey {
case tabRenderer = "tabRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
tabRenderer = try values.decodeIfPresent(TabRenderer.self, forKey: .tabRenderer)
}
struct TabRenderer: Codable {
let content:Content?
enum CodingKeys: String, CodingKey {
case content = "content"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
content = try values.decodeIfPresent(Content.self, forKey: .content)
}
struct Content: Codable {
let sectionListRenderer:SectionListRenderer?
enum CodingKeys: String, CodingKey {
case sectionListRenderer = "sectionListRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
sectionListRenderer = try values.decodeIfPresent(SectionListRenderer.self, forKey: .sectionListRenderer)
}
struct SectionListRenderer: Codable {
let contents:[Content]?
enum CodingKeys: String, CodingKey {
case contents = "contents"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
contents = try values.decodeIfPresent([Content].self, forKey: .contents)
}
struct Content: Codable {
///
let musicPlaylistShelfRenderer:MusicPlaylistShelfRenderer?
///
let gridRenderer:GridRenderer?
enum CodingKeys: String, CodingKey {
case musicPlaylistShelfRenderer = "musicPlaylistShelfRenderer"
case gridRenderer = "gridRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicPlaylistShelfRenderer = try values.decodeIfPresent(MusicPlaylistShelfRenderer.self, forKey: .musicPlaylistShelfRenderer)
gridRenderer = try values.decodeIfPresent(GridRenderer.self, forKey: .gridRenderer)
}
struct MusicPlaylistShelfRenderer: Codable {
///id
let playlistId:String?
///
let contents:[Content]?
///
let continuations:[Continuation]?
enum CodingKeys: String, CodingKey {
case playlistId = "playlistId"
case contents = "contents"
case continuations = "continuations"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
playlistId = try values.decodeIfPresent(String.self, forKey: .playlistId)
contents = try values.decodeIfPresent([Content].self, forKey: .contents)
continuations = try values.decodeIfPresent([Continuation].self, forKey: .continuations)
}
struct Content: Codable {
let musicResponsiveListItemRenderer: RootMusicResponsiveListItemRenderer?
enum CodingKeys: String, CodingKey {
case musicResponsiveListItemRenderer = "musicResponsiveListItemRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicResponsiveListItemRenderer = try values.decodeIfPresent(RootMusicResponsiveListItemRenderer.self, forKey: .musicResponsiveListItemRenderer)
}
}
struct Continuation: Codable {
let nextContinuationData:NextContinuationData?
enum CodingKeys: String, CodingKey {
case nextContinuationData = "nextContinuationData"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
nextContinuationData = try values.decodeIfPresent(NextContinuationData.self, forKey: .nextContinuationData)
}
struct NextContinuationData: Codable {
let continuation:String?
let clickTrackingParams:String?
enum CodingKeys: String, CodingKey {
case continuation = "continuation"
case clickTrackingParams = "clickTrackingParams"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
continuation = try values.decodeIfPresent(String.self, forKey: .continuation)
clickTrackingParams = try values.decodeIfPresent(String.self, forKey: .clickTrackingParams)
}
}
}
}
struct GridRenderer: Codable {
///
let items:[Item]?
///
let continuations:[Continuation]?
enum CodingKeys: String, CodingKey {
case items = "items"
case continuations = "continuations"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
items = try values.decodeIfPresent([Item].self, forKey: .items)
continuations = try values.decodeIfPresent([Continuation].self, forKey: .continuations)
}
struct Item: Codable {
let musicTwoRowItemRenderer: RootMusicTwoRowItemRenderer?
enum CodingKeys: String, CodingKey {
case musicTwoRowItemRenderer = "musicTwoRowItemRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicTwoRowItemRenderer = try values.decodeIfPresent(RootMusicTwoRowItemRenderer.self, forKey: .musicTwoRowItemRenderer)
}
}
struct Continuation: Codable {
let nextContinuationData:NextContinuationData?
enum CodingKeys: String, CodingKey {
case nextContinuationData = "nextContinuationData"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
nextContinuationData = try values.decodeIfPresent(NextContinuationData.self, forKey: .nextContinuationData)
}
struct NextContinuationData: Codable {
let continuation:String?
let clickTrackingParams:String?
enum CodingKeys: String, CodingKey {
case continuation = "continuation"
case clickTrackingParams = "clickTrackingParams"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
continuation = try values.decodeIfPresent(String.self, forKey: .continuation)
clickTrackingParams = try values.decodeIfPresent(String.self, forKey: .clickTrackingParams)
}
}
}
}
}
}
}
}
}
}
}
struct ContinuationContents: Codable {
let musicPlaylistShelfContinuation:MusicPlaylistShelfContinuation?
enum CodingKeys: String, CodingKey {
case musicPlaylistShelfContinuation = "musicPlaylistShelfContinuation"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicPlaylistShelfContinuation = try values.decodeIfPresent(MusicPlaylistShelfContinuation.self, forKey: .musicPlaylistShelfContinuation)
}
struct MusicPlaylistShelfContinuation: Codable {
///
let contents:[Content]?
///
let continuations : [Continuation]?
enum CodingKeys: String, CodingKey {
case contents = "contents"
case continuations = "continuations"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
contents = try values.decodeIfPresent([Content].self, forKey: .contents)
continuations = try values.decodeIfPresent([Continuation].self, forKey: .continuations)
}
struct Content: Codable {
let musicResponsiveListItemRenderer:RootMusicResponsiveListItemRenderer?
let musicTwoRowItemRenderer:RootMusicTwoRowItemRenderer?
enum CodingKeys: String, CodingKey {
case musicResponsiveListItemRenderer = "musicResponsiveListItemRenderer"
case musicTwoRowItemRenderer = "musicTwoRowItemRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicResponsiveListItemRenderer = try values.decodeIfPresent(RootMusicResponsiveListItemRenderer.self, forKey: .musicResponsiveListItemRenderer)
musicTwoRowItemRenderer = try values.decodeIfPresent(RootMusicTwoRowItemRenderer.self, forKey: .musicTwoRowItemRenderer)
}
}
///
struct Continuation: Codable {
let nextContinuationData:NextContinuationData?
enum CodingKeys: String, CodingKey {
case nextContinuationData = "nextContinuationData"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
nextContinuationData = try values.decodeIfPresent(NextContinuationData.self, forKey: .nextContinuationData)
}
//MARK: -
struct NextContinuationData: Codable {
let continuation:String?
let clickTrackingParams:String?
enum CodingKeys: String, CodingKey {
case continuation = "continuation"
case clickTrackingParams = "clickTrackingParams"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
continuation = try values.decodeIfPresent(String.self, forKey: .continuation)
clickTrackingParams = try values.decodeIfPresent(String.self, forKey: .clickTrackingParams)
}
}
}
}
}
}

View File

@ -70,15 +70,12 @@ struct JsonBrowses: Codable {
struct TabRenderer : Codable {
///contentctoke
let content : Content?
let trackingParams : String?
enum CodingKeys: String, CodingKey {
case content = "content"
case trackingParams = "trackingParams"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
content = try values.decodeIfPresent(Content.self, forKey: .content)
trackingParams = try values.decodeIfPresent(String.self, forKey: .trackingParams)
}
struct Content : Codable {
let sectionListRenderer : SectionListRenderer?
@ -124,7 +121,7 @@ struct JsonBrowses: Codable {
struct MusicCarouselShelfRenderer: Codable {
///
let header:Header?
///
///
let contents:[Content]?
enum CodingKeys: String, CodingKey {
case header = "header"
@ -497,24 +494,32 @@ struct JsonBrowses: Codable {
}
//MARK: -
////(ID)
////(ID)
struct RootMusicResponsiveListItemRenderer: Codable {
///
let thumbnail:Thumbnail?
///(3013/)
///(0)
let flexColumns:[FlexColumn]?
///IDID
let overlay:Overlay?
///flexColumnsplayListId
let menu:Menu?
////IDplaylistItemData/
let playlistItemData:PlaylistItemData?
////IDnavigationEndpoint/
let navigationEndpoint:NavigationEndpoint?
enum CodingKeys: String, CodingKey {
case thumbnail = "thumbnail"
case overlay = "overlay"
case flexColumns = "flexColumns"
case menu = "menu"
case playlistItemData = "playlistItemData"
case navigationEndpoint = "navigationEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
thumbnail = try values.decodeIfPresent(Thumbnail.self, forKey: .thumbnail)
overlay = try values.decodeIfPresent(Overlay.self, forKey: .overlay)
flexColumns = try values.decodeIfPresent([FlexColumn].self, forKey: .flexColumns)
menu = try values.decodeIfPresent(Menu.self, forKey: .menu)
playlistItemData = try values.decodeIfPresent(PlaylistItemData.self, forKey: .playlistItemData)
navigationEndpoint = try values.decodeIfPresent(NavigationEndpoint.self, forKey: .navigationEndpoint)
}
//
struct Thumbnail: Codable {
@ -551,18 +556,12 @@ struct RootMusicResponsiveListItemRenderer: Codable {
struct Thumbnails: Codable {
///
let url:String?
let width:CGFloat?
let height:CGFloat?
enum CodingKeys: String, CodingKey {
case url = "url"
case width = "width"
case height = "height"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
url = try values.decodeIfPresent(String.self, forKey: .url)
width = try values.decodeIfPresent(CGFloat.self, forKey: .width)
height = try values.decodeIfPresent(CGFloat.self, forKey: .height)
}
}
}
@ -596,11 +595,9 @@ struct RootMusicResponsiveListItemRenderer: Codable {
let values = try decoder.container(keyedBy: CodingKeys.self)
runs = try values.decodeIfPresent([Run].self, forKey: .runs)
}
//MARK: - //
struct Run: Codable {
///
///
let text:String?
///ID
let navigationEndpoint:NavigationEndpoint?
enum CodingKeys: String, CodingKey {
case text = "text"
@ -612,23 +609,113 @@ struct RootMusicResponsiveListItemRenderer: Codable {
navigationEndpoint = try values.decodeIfPresent(NavigationEndpoint.self, forKey: .navigationEndpoint)
}
struct NavigationEndpoint: Codable {
let browseEndpoint:BrowseEndpoint?
let watchEndpoint:WatchEndpoint?
enum CodingKeys: String, CodingKey {
case watchEndpoint = "watchEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
watchEndpoint = try values.decodeIfPresent(WatchEndpoint.self, forKey: .watchEndpoint)
}
struct WatchEndpoint: Codable {
let playlistId:String?
enum CodingKeys: String, CodingKey {
case playlistId = "playlistId"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
playlistId = try values.decodeIfPresent(String.self, forKey: .playlistId)
}
}
}
}
}
}
}
//
struct Menu: Codable {
let menuRenderer:MenuRenderer?
enum CodingKeys: String, CodingKey {
case menuRenderer = "menuRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
menuRenderer = try values.decodeIfPresent(MenuRenderer.self, forKey: .menuRenderer)
}
struct MenuRenderer: Codable {
let items:[Item]?
enum CodingKeys: String, CodingKey {
case items = "items"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
items = try values.decodeIfPresent([Item].self, forKey: .items)
}
struct Item: Codable {
let menuNavigationItemRenderer:MenuNavigationItemRenderer?
enum CodingKeys: String, CodingKey {
case menuNavigationItemRenderer = "menuNavigationItemRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
menuNavigationItemRenderer = try values.decodeIfPresent(MenuNavigationItemRenderer.self, forKey: .menuNavigationItemRenderer)
}
struct MenuNavigationItemRenderer: Codable {
let navigationEndpoint:NavigationEndpoint?
enum CodingKeys: String, CodingKey {
case navigationEndpoint = "navigationEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
navigationEndpoint = try values.decodeIfPresent(NavigationEndpoint.self, forKey: .navigationEndpoint)
}
struct NavigationEndpoint: Codable {
let watchEndpoint:WatchEndpoint?
enum CodingKeys: String, CodingKey {
case watchEndpoint = "watchEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
watchEndpoint = try values.decodeIfPresent(WatchEndpoint.self, forKey: .watchEndpoint)
}
struct WatchEndpoint: Codable {
let playlistId:String?
enum CodingKeys: String, CodingKey {
case playlistId = "playlistId"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
playlistId = try values.decodeIfPresent(String.self, forKey: .playlistId)
}
}
}
}
}
}
}
struct PlaylistItemData: Codable {
let videoId:String?
enum CodingKeys: String, CodingKey {
case videoId = "videoId"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
videoId = try values.decodeIfPresent(String.self, forKey: .videoId)
}
}
struct NavigationEndpoint: Codable {
let browseEndpoint:BrowseEndpoint?
enum CodingKeys: String, CodingKey {
case browseEndpoint = "browseEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
browseEndpoint = try values.decodeIfPresent(BrowseEndpoint.self, forKey: .browseEndpoint)
}
//MARK: -
struct BrowseEndpoint: Codable {
///ID//
let browseId:String?
/////
let params:String?
///
let browseEndpointContextSupportedConfigs:BrowseEndpointContextSupportedConfigs?
enum CodingKeys: String, CodingKey {
case browseId = "browseId"
@ -650,7 +737,6 @@ struct RootMusicResponsiveListItemRenderer: Codable {
let values = try decoder.container(keyedBy: CodingKeys.self)
browseEndpointContextMusicConfig = try values.decodeIfPresent(BrowseEndpointContextMusicConfig.self, forKey: .browseEndpointContextMusicConfig)
}
//MARK: - Browse //
struct BrowseEndpointContextMusicConfig: Codable {
let pageType:String?
enum CodingKeys: String, CodingKey {
@ -664,78 +750,181 @@ struct RootMusicResponsiveListItemRenderer: Codable {
}
}
}
}
}
}
}
//IDID
struct Overlay: Codable {
let musicItemThumbnailOverlayRenderer:MusicItemThumbnailOverlayRenderer?
////(ID)
struct RootMusicTwoRowItemRenderer: Codable {
///
let thumbnailRenderer:ThumbnailRenderer?
///
let title:Title?
///
let subtitle:Subtitle?
///iD
let navigationEndpoint:NavigationEndpoint?
enum CodingKeys: String, CodingKey {
case musicItemThumbnailOverlayRenderer = "musicItemThumbnailOverlayRenderer"
case thumbnailRenderer = "thumbnailRenderer"
case title = "title"
case subtitle = "subtitle"
case navigationEndpoint = "navigationEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicItemThumbnailOverlayRenderer = try values.decodeIfPresent(MusicItemThumbnailOverlayRenderer.self, forKey: .musicItemThumbnailOverlayRenderer)
thumbnailRenderer = try values.decodeIfPresent(ThumbnailRenderer.self, forKey: .thumbnailRenderer)
title = try values.decodeIfPresent(Title.self, forKey: .title)
subtitle = try values.decodeIfPresent(Subtitle.self, forKey: .subtitle)
navigationEndpoint = try values.decodeIfPresent(NavigationEndpoint.self, forKey: .navigationEndpoint)
}
struct MusicItemThumbnailOverlayRenderer: Codable {
let content:Content?
struct ThumbnailRenderer: Codable {
let musicThumbnailRenderer:MusicThumbnailRenderer?
enum CodingKeys: String, CodingKey {
case content = "content"
case musicThumbnailRenderer = "musicThumbnailRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
content = try values.decodeIfPresent(Content.self, forKey: .content)
musicThumbnailRenderer = try values.decodeIfPresent(MusicThumbnailRenderer.self, forKey: .musicThumbnailRenderer)
}
struct Content: Codable {
let musicPlayButtonRenderer:MusicPlayButtonRenderer?
struct MusicThumbnailRenderer: Codable {
let thumbnail:Thumbnail?
enum CodingKeys: String, CodingKey {
case musicPlayButtonRenderer = "musicPlayButtonRenderer"
case thumbnail = "thumbnail"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicPlayButtonRenderer = try values.decodeIfPresent(MusicPlayButtonRenderer.self, forKey: .musicPlayButtonRenderer)
thumbnail = try values.decodeIfPresent(Thumbnail.self, forKey: .thumbnail)
}
struct MusicPlayButtonRenderer: Codable {
let playNavigationEndpoint:PlayNavigationEndpoint?
struct Thumbnail: Codable {
let thumbnails:[Thumbnails]?
enum CodingKeys: String, CodingKey {
case playNavigationEndpoint = "playNavigationEndpoint"
case thumbnails = "thumbnails"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
playNavigationEndpoint = try values.decodeIfPresent(PlayNavigationEndpoint.self, forKey: .playNavigationEndpoint)
thumbnails = try values.decodeIfPresent([Thumbnails].self, forKey: .thumbnails)
}
struct PlayNavigationEndpoint: Codable {
struct Thumbnails: Codable {
let url:String?
enum CodingKeys: String, CodingKey {
case url = "url"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
url = try values.decodeIfPresent(String.self, forKey: .url)
}
}
}
}
}
struct Title: Codable {
let runs:[Run]?
enum CodingKeys: String, CodingKey {
case runs = "runs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
runs = try values.decodeIfPresent([Run].self, forKey: .runs)
}
struct Run: Codable {
let text:String?
enum CodingKeys: String, CodingKey {
case text = "text"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
text = try values.decodeIfPresent(String.self, forKey: .text)
}
}
}
struct Subtitle: Codable {
let runs:[Run]?
enum CodingKeys: String, CodingKey {
case runs = "runs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
runs = try values.decodeIfPresent([Run].self, forKey: .runs)
}
struct Run: Codable {
let text:String?
enum CodingKeys: String, CodingKey {
case text = "text"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
text = try values.decodeIfPresent(String.self, forKey: .text)
}
}
}
struct NavigationEndpoint: Codable {
////
let browseEndpoint:BrowseEndpoint?
///
let watchEndpoint:WatchEndpoint?
enum CodingKeys: String, CodingKey {
case browseEndpoint = "browseEndpoint"
case watchEndpoint = "watchEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
browseEndpoint = try values.decodeIfPresent(BrowseEndpoint.self, forKey: .browseEndpoint)
watchEndpoint = try values.decodeIfPresent(WatchEndpoint.self, forKey: .watchEndpoint)
}
//MARK: - ID/ID/
struct WatchEndpoint: Codable {
///ID
let videoId:String?
///
let playlistId:String?
///
struct BrowseEndpoint: Codable {
let browseId:String?
let params:String?
///
let browseEndpointContextSupportedConfigs:BrowseEndpointContextSupportedConfigs?
enum CodingKeys: String, CodingKey {
case browseId = "browseId"
case params = "params"
case browseEndpointContextSupportedConfigs = "browseEndpointContextSupportedConfigs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
browseId = try values.decodeIfPresent(String.self, forKey: .browseId)
params = try values.decodeIfPresent(String.self, forKey: .params)
browseEndpointContextSupportedConfigs = try values.decodeIfPresent(BrowseEndpointContextSupportedConfigs.self, forKey: .browseEndpointContextSupportedConfigs)
}
struct BrowseEndpointContextSupportedConfigs: Codable {
let browseEndpointContextMusicConfig:BrowseEndpointContextMusicConfig?
enum CodingKeys: String, CodingKey {
case browseEndpointContextMusicConfig = "browseEndpointContextMusicConfig"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
browseEndpointContextMusicConfig = try values.decodeIfPresent(BrowseEndpointContextMusicConfig.self, forKey: .browseEndpointContextMusicConfig)
}
struct BrowseEndpointContextMusicConfig: Codable {
let pageType:String?
enum CodingKeys: String, CodingKey {
case pageType = "pageType"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
pageType = try values.decodeIfPresent(String.self, forKey: .pageType)
}
}
}
}
struct WatchEndpoint: Codable {
///ID
let videoId:String?
///ID
let playlistId:String?
///
let watchEndpointMusicSupportedConfigs:WatchEndpointMusicSupportedConfigs?
enum CodingKeys: String, CodingKey {
case videoId = "videoId"
case playlistId = "playlistId"
case params = "params"
case watchEndpointMusicSupportedConfigs = "watchEndpointMusicSupportedConfigs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
videoId = try values.decodeIfPresent(String.self, forKey: .videoId)
playlistId = try values.decodeIfPresent(String.self, forKey: .playlistId)
params = try values.decodeIfPresent(String.self, forKey: .params)
watchEndpointMusicSupportedConfigs = try values.decodeIfPresent(WatchEndpointMusicSupportedConfigs.self, forKey: .watchEndpointMusicSupportedConfigs)
}
struct WatchEndpointMusicSupportedConfigs: Codable {
@ -747,9 +936,7 @@ struct RootMusicResponsiveListItemRenderer: Codable {
let values = try decoder.container(keyedBy: CodingKeys.self)
watchEndpointMusicConfig = try values.decodeIfPresent(WatchEndpointMusicConfig.self, forKey: .watchEndpointMusicConfig)
}
//MARK: - Video
struct WatchEndpointMusicConfig: Codable {
///Video
let musicVideoType:String?
enum CodingKeys: String, CodingKey {
case musicVideoType = "musicVideoType"
@ -763,331 +950,3 @@ struct RootMusicResponsiveListItemRenderer: Codable {
}
}
}
}
}
}
}
////(ID)
struct RootMusicTwoRowItemRenderer: Codable {
///
let thumbnailRenderer:ThumbnailRenderer?
///
let title:Title?
///
let subtitle:Subtitle?
///IDID
let thumbnailOverlay:ThumbnailOverlay?
///
let navigationEndpoint:NavigationEndpoint?
enum CodingKeys: String, CodingKey {
case thumbnailRenderer = "thumbnailRenderer"
case title = "title"
case subtitle = "subtitle"
case thumbnailOverlay = "thumbnailOverlay"
case navigationEndpoint = "navigationEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.thumbnailRenderer = try values.decodeIfPresent(ThumbnailRenderer.self, forKey: .thumbnailRenderer)
self.title = try values.decodeIfPresent(Title.self, forKey: .title)
self.subtitle = try values.decodeIfPresent(Subtitle.self, forKey: .subtitle)
self.thumbnailOverlay = try values.decodeIfPresent(ThumbnailOverlay.self, forKey: .thumbnailOverlay)
self.navigationEndpoint = try values.decodeIfPresent(NavigationEndpoint.self, forKey: .navigationEndpoint)
}
struct ThumbnailRenderer: Codable {
let musicThumbnailRenderer:MusicThumbnailRenderer?
enum CodingKeys: String, CodingKey {
case musicThumbnailRenderer = "musicThumbnailRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.musicThumbnailRenderer = try values.decodeIfPresent(MusicThumbnailRenderer.self, forKey: .musicThumbnailRenderer)
}
struct MusicThumbnailRenderer: Codable {
let thumbnail:Thumbnail?
enum CodingKeys: String, CodingKey {
case thumbnail = "thumbnail"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.thumbnail = try values.decodeIfPresent(Thumbnail.self, forKey: .thumbnail)
}
struct Thumbnail: Codable {
///
let thumbnails:[Thumbnails]?
enum CodingKeys: String, CodingKey {
case thumbnails = "thumbnails"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.thumbnails = try values.decodeIfPresent([Thumbnails].self, forKey: .thumbnails)
}
//MARK: -
///
struct Thumbnails: Codable {
///
let url:String?
let width:CGFloat?
let height:CGFloat?
enum CodingKeys: String, CodingKey {
case url = "url"
case width = "width"
case height = "height"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
url = try values.decodeIfPresent(String.self, forKey: .url)
width = try values.decodeIfPresent(CGFloat.self, forKey: .width)
height = try values.decodeIfPresent(CGFloat.self, forKey: .height)
}
}
}
}
}
struct Title: Codable {
let runs:[Run]?
enum CodingKeys: String, CodingKey {
case runs = "runs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.runs = try values.decodeIfPresent([Run].self, forKey: .runs)
}
//MARK: - //
struct Run: Codable {
///
let text:String?
///ID
let navigationEndpoint: NavigationEndpoint?
enum CodingKeys: String, CodingKey {
case text = "text"
case navigationEndpoint = "navigationEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
text = try values.decodeIfPresent(String.self, forKey: .text)
navigationEndpoint = try values.decodeIfPresent(NavigationEndpoint.self, forKey: .navigationEndpoint)
}
struct NavigationEndpoint: Codable {
let browseEndpoint:BrowseEndpoint?
enum CodingKeys: String, CodingKey {
case browseEndpoint = "browseEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
browseEndpoint = try values.decodeIfPresent(BrowseEndpoint.self, forKey: .browseEndpoint)
}
//MARK: -
struct BrowseEndpoint: Codable {
///ID//
let browseId:String?
/////
let params:String?
///
let browseEndpointContextSupportedConfigs:BrowseEndpointContextSupportedConfigs?
enum CodingKeys: String, CodingKey {
case browseId = "browseId"
case params = "params"
case browseEndpointContextSupportedConfigs = "browseEndpointContextSupportedConfigs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
browseId = try values.decodeIfPresent(String.self, forKey: .browseId)
params = try values.decodeIfPresent(String.self, forKey: .params)
browseEndpointContextSupportedConfigs = try values.decodeIfPresent(BrowseEndpointContextSupportedConfigs.self, forKey: .browseEndpointContextSupportedConfigs)
}
struct BrowseEndpointContextSupportedConfigs: Codable {
let browseEndpointContextMusicConfig:BrowseEndpointContextMusicConfig?
enum CodingKeys: String, CodingKey {
case browseEndpointContextMusicConfig = "browseEndpointContextMusicConfig"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
browseEndpointContextMusicConfig = try values.decodeIfPresent(BrowseEndpointContextMusicConfig.self, forKey: .browseEndpointContextMusicConfig)
}
//MARK: - Browse //
struct BrowseEndpointContextMusicConfig: Codable {
let pageType:String?
enum CodingKeys: String, CodingKey {
case pageType = "pageType"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
pageType = try values.decodeIfPresent(String.self, forKey: .pageType)
}
}
}
}
}
}
}
struct Subtitle: Codable {
let runs:[Run]?
enum CodingKeys: String, CodingKey {
case runs = "runs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.runs = try values.decodeIfPresent([Run].self, forKey: .runs)
}
//MARK: - //
struct Run: Codable {
///
let text:String?
///ID
let navigationEndpoint: NavigationEndpoint?
enum CodingKeys: String, CodingKey {
case text = "text"
case navigationEndpoint = "navigationEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
text = try values.decodeIfPresent(String.self, forKey: .text)
navigationEndpoint = try values.decodeIfPresent(NavigationEndpoint.self, forKey: .navigationEndpoint)
}
struct NavigationEndpoint: Codable {
let browseEndpoint:BrowseEndpoint?
enum CodingKeys: String, CodingKey {
case browseEndpoint = "browseEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
browseEndpoint = try values.decodeIfPresent(BrowseEndpoint.self, forKey: .browseEndpoint)
}
//MARK: -
struct BrowseEndpoint: Codable {
///ID//
let browseId:String?
/////
let params:String?
///
let browseEndpointContextSupportedConfigs:BrowseEndpointContextSupportedConfigs?
enum CodingKeys: String, CodingKey {
case browseId = "browseId"
case params = "params"
case browseEndpointContextSupportedConfigs = "browseEndpointContextSupportedConfigs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
browseId = try values.decodeIfPresent(String.self, forKey: .browseId)
params = try values.decodeIfPresent(String.self, forKey: .params)
browseEndpointContextSupportedConfigs = try values.decodeIfPresent(BrowseEndpointContextSupportedConfigs.self, forKey: .browseEndpointContextSupportedConfigs)
}
struct BrowseEndpointContextSupportedConfigs: Codable {
let browseEndpointContextMusicConfig:BrowseEndpointContextMusicConfig?
enum CodingKeys: String, CodingKey {
case browseEndpointContextMusicConfig = "browseEndpointContextMusicConfig"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
browseEndpointContextMusicConfig = try values.decodeIfPresent(BrowseEndpointContextMusicConfig.self, forKey: .browseEndpointContextMusicConfig)
}
//MARK: - Browse //
struct BrowseEndpointContextMusicConfig: Codable {
let pageType:String?
enum CodingKeys: String, CodingKey {
case pageType = "pageType"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
pageType = try values.decodeIfPresent(String.self, forKey: .pageType)
}
}
}
}
}
}
}
struct ThumbnailOverlay: Codable {
let musicItemThumbnailOverlayRenderer:MusicItemThumbnailOverlayRenderer?
enum CodingKeys: String, CodingKey {
case musicItemThumbnailOverlayRenderer = "musicItemThumbnailOverlayRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.musicItemThumbnailOverlayRenderer = try values.decodeIfPresent(MusicItemThumbnailOverlayRenderer.self, forKey: .musicItemThumbnailOverlayRenderer)
}
struct MusicItemThumbnailOverlayRenderer: Codable {
let content:Content?
enum CodingKeys: String, CodingKey {
case content = "content"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.content = try values.decodeIfPresent(Content.self, forKey: .content)
}
struct Content: Codable {
let musicPlayButtonRenderer:MusicPlayButtonRenderer?
enum CodingKeys: String, CodingKey {
case musicPlayButtonRenderer = "musicPlayButtonRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.musicPlayButtonRenderer = try values.decodeIfPresent(MusicPlayButtonRenderer.self, forKey: .musicPlayButtonRenderer)
}
struct MusicPlayButtonRenderer: Codable {
let playNavigationEndpoint:PlayNavigationEndpoint?
enum CodingKeys: String, CodingKey {
case playNavigationEndpoint = "playNavigationEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.playNavigationEndpoint = try values.decodeIfPresent(PlayNavigationEndpoint.self, forKey: .playNavigationEndpoint)
}
struct PlayNavigationEndpoint: Codable {
let watchPlaylistEndpoint:WatchPlaylistEndpoint?
enum CodingKeys: String, CodingKey {
case watchPlaylistEndpoint = "watchPlaylistEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
watchPlaylistEndpoint = try values.decodeIfPresent(WatchPlaylistEndpoint.self, forKey: .watchPlaylistEndpoint)
}
//MARK: - /ID
struct WatchPlaylistEndpoint: Codable {
///
let playlistId:String?
enum CodingKeys: String, CodingKey {
case playlistId = "playlistId"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
playlistId = try values.decodeIfPresent(String.self, forKey: .playlistId)
}
}
}
}
}
}
}
struct NavigationEndpoint: Codable {
let watchEndpoint:WatchEndpoint?
enum CodingKeys: String, CodingKey {
case watchEndpoint = "watchEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.watchEndpoint = try values.decodeIfPresent(WatchEndpoint.self, forKey: .watchEndpoint)
}
struct WatchEndpoint: Codable {
let videoId:String?
enum CodingKeys: String, CodingKey {
case videoId = "videoId"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.videoId = try values.decodeIfPresent(String.self, forKey: .videoId)
}
}
}
}

View File

@ -0,0 +1,119 @@
//
// MPPositive_Recommend.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/20.
//
import UIKit
struct JsonRecommend: Codable {
let contents:Contents?
enum CodingKeys: String, CodingKey {
case contents = "contents"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
contents = try values.decodeIfPresent(Contents.self, forKey: .contents)
}
struct Contents: Codable {
let sectionListRenderer:SectionListRenderer?
enum CodingKeys: String, CodingKey {
case sectionListRenderer = "sectionListRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
sectionListRenderer = try values.decodeIfPresent(SectionListRenderer.self, forKey: .sectionListRenderer)
}
struct SectionListRenderer: Codable {
let contents:[Content]?
enum CodingKeys: String, CodingKey {
case contents = "contents"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
contents = try values.decodeIfPresent([Content].self, forKey: .contents)
}
struct Content: Codable {
let musicCarouselShelfRenderer:MusicCarouselShelfRenderer?
enum CodingKeys: String, CodingKey {
case musicCarouselShelfRenderer = "musicCarouselShelfRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicCarouselShelfRenderer = try values.decodeIfPresent(MusicCarouselShelfRenderer.self, forKey: .musicCarouselShelfRenderer)
}
struct MusicCarouselShelfRenderer: Codable {
///
let header:Header?
///
let contents:[Content]?
enum CodingKeys: String, CodingKey {
case header = "header"
case contents = "contents"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
header = try values.decodeIfPresent(Header.self, forKey: .header)
contents = try values.decodeIfPresent([Content].self, forKey: .contents)
}
struct Header: Codable {
let musicCarouselShelfBasicHeaderRenderer:MusicCarouselShelfBasicHeaderRenderer?
enum CodingKeys: String, CodingKey {
case musicCarouselShelfBasicHeaderRenderer = "musicCarouselShelfBasicHeaderRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicCarouselShelfBasicHeaderRenderer = try values.decodeIfPresent(MusicCarouselShelfBasicHeaderRenderer.self, forKey: .musicCarouselShelfBasicHeaderRenderer)
}
struct MusicCarouselShelfBasicHeaderRenderer: Codable {
let title:Title?
enum CodingKeys: String, CodingKey {
case title = "title"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
title = try values.decodeIfPresent(Title.self, forKey: .title)
}
struct Title: Codable {
let runs:[Run]?
enum CodingKeys: String, CodingKey {
case runs = "runs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
runs = try values.decodeIfPresent([Run].self, forKey: .runs)
}
struct Run: Codable {
///
let text:String?
enum CodingKeys: String, CodingKey {
case text = "text"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
text = try values.decodeIfPresent(String.self, forKey: .text)
}
}
}
}
}
struct Content: Codable {
let musicResponsiveListItemRenderer:RootMusicResponsiveListItemRenderer?
let musicTwoRowItemRenderer:RootMusicTwoRowItemRenderer?
enum CodingKeys: String, CodingKey {
case musicResponsiveListItemRenderer = "musicResponsiveListItemRenderer"
case musicTwoRowItemRenderer = "musicTwoRowItemRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicResponsiveListItemRenderer = try values.decodeIfPresent(RootMusicResponsiveListItemRenderer.self, forKey: .musicResponsiveListItemRenderer)
musicTwoRowItemRenderer = try values.decodeIfPresent(RootMusicTwoRowItemRenderer.self, forKey: .musicTwoRowItemRenderer)
}
}
}
}
}
}
}

View File

@ -0,0 +1,469 @@
//
// MPPositive_JsonSearchResults.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/12.
//
import UIKit
//MARK: -
///
struct JsonSearchPreviewResults: Codable {
let contents:Contents?
enum CodingKeys: String, CodingKey {
case contents = "contents"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
contents = try values.decodeIfPresent(Contents.self, forKey: .contents)
}
struct Contents: Codable {
let tabbedSearchResultsRenderer:TabbedSearchResultsRenderer?
enum CodingKeys: String, CodingKey {
case tabbedSearchResultsRenderer = "tabbedSearchResultsRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
tabbedSearchResultsRenderer = try values.decodeIfPresent(TabbedSearchResultsRenderer.self, forKey: .tabbedSearchResultsRenderer)
}
struct TabbedSearchResultsRenderer: Codable {
let tabs:[Tab]?
enum CodingKeys: String, CodingKey {
case tabs = "tabs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
tabs = try values.decodeIfPresent([Tab].self, forKey: .tabs)
}
struct Tab:Codable {
let tabRenderer:TabRenderer?
enum CodingKeys: String, CodingKey {
case tabRenderer = "tabRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
tabRenderer = try values.decodeIfPresent(TabRenderer.self, forKey: .tabRenderer)
}
struct TabRenderer: Codable {
let content:Content?
enum CodingKeys: String, CodingKey {
case content = "content"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
content = try values.decodeIfPresent(Content.self, forKey: .content)
}
struct Content: Codable {
let sectionListRenderer:SectionListRenderer?
enum CodingKeys: String, CodingKey {
case sectionListRenderer = "sectionListRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
sectionListRenderer = try values.decodeIfPresent(SectionListRenderer.self, forKey: .sectionListRenderer)
}
struct SectionListRenderer: Codable {
///
let contents:[Content]?
enum CodingKeys: String, CodingKey {
case contents = "contents"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
contents = try values.decodeIfPresent([Content].self, forKey: .contents)
}
//MARK: -
struct Content: Codable {
///
let musicCardShelfRenderer:MusicCardShelfRenderer?
///
let musicShelfRenderer:MusicShelfRenderer?
enum CodingKeys: String, CodingKey {
case musicCardShelfRenderer = "musicCardShelfRenderer"
case musicShelfRenderer = "musicShelfRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicCardShelfRenderer = try values.decodeIfPresent(MusicCardShelfRenderer.self, forKey: .musicCardShelfRenderer)
musicShelfRenderer = try values.decodeIfPresent(MusicShelfRenderer.self, forKey: .musicShelfRenderer)
}
//MARK: -
///
struct MusicCardShelfRenderer: Codable {
///
let thumbnail:Thumbnail?
///(ID)
let title:Title?
///
let subtitle:Subtitle?
///flexColumnsplayListId
let menu:Menu?
///
let header:Header?
///,0
let contents:[Content]?
enum CodingKeys: String, CodingKey {
case thumbnail = "thumbnail"
case title = "title"
case subtitle = "subtitle"
case menu = "menu"
case header = "header"
case contents = "contents"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
thumbnail = try values.decodeIfPresent(Thumbnail.self, forKey: .thumbnail)
title = try values.decodeIfPresent(Title.self, forKey: .title)
subtitle = try values.decodeIfPresent(Subtitle.self, forKey: .subtitle)
menu = try values.decodeIfPresent(Menu.self, forKey: .menu)
header = try values.decodeIfPresent(Header.self, forKey: .header)
contents = try values.decodeIfPresent([Content].self, forKey: .contents)
}
///
struct Thumbnail: Codable {
let musicThumbnailRenderer:MusicThumbnailRenderer?
enum CodingKeys: String, CodingKey {
case musicThumbnailRenderer = "musicThumbnailRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicThumbnailRenderer = try values.decodeIfPresent(MusicThumbnailRenderer.self, forKey: .musicThumbnailRenderer)
}
struct MusicThumbnailRenderer: Codable {
let thumbnail:Thumbnail?
enum CodingKeys: String, CodingKey {
case thumbnail = "thumbnail"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
thumbnail = try values.decodeIfPresent(Thumbnail.self, forKey: .thumbnail)
}
struct Thumbnail: Codable {
let thumbnails:[Thumbnails]?
enum CodingKeys: String, CodingKey {
case thumbnails = "thumbnails"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
thumbnails = try values.decodeIfPresent([Thumbnails].self, forKey: .thumbnails)
}
struct Thumbnails: Codable {
let url:String?
enum CodingKeys: String, CodingKey {
case url = "url"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
url = try values.decodeIfPresent(String.self, forKey: .url)
}
}
}
}
}
///
struct Title: Codable {
let runs:[Run]?
enum CodingKeys: String, CodingKey {
case runs = "runs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
runs = try values.decodeIfPresent([Run].self, forKey: .runs)
}
struct Run: Codable {
///
let text:String?
let navigationEndpoint:NavigationEndpoint?
enum CodingKeys: String, CodingKey {
case text = "text"
case navigationEndpoint = "navigationEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
text = try values.decodeIfPresent(String.self, forKey: .text)
navigationEndpoint = try values.decodeIfPresent(NavigationEndpoint.self, forKey: .navigationEndpoint)
}
struct NavigationEndpoint: Codable {
///
let watchEndpoint:WatchEndpoint?
/////
let browseEndpoint:BrowseEndpoint?
enum CodingKeys: String, CodingKey {
case watchEndpoint = "watchEndpoint"
case browseEndpoint = "browseEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
watchEndpoint = try values.decodeIfPresent(WatchEndpoint.self, forKey: .watchEndpoint)
browseEndpoint = try values.decodeIfPresent(BrowseEndpoint.self, forKey: .browseEndpoint)
}
struct WatchEndpoint: Codable {
let videoId:String?
enum CodingKeys: String, CodingKey {
case videoId = "videoId"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
videoId = try values.decodeIfPresent(String.self, forKey: .videoId)
}
}
struct BrowseEndpoint: Codable {
let browseId:String?
let browseEndpointContextSupportedConfigs:BrowseEndpointContextSupportedConfigs?
enum CodingKeys: String, CodingKey {
case browseId = "browseId"
case browseEndpointContextSupportedConfigs = "browseEndpointContextSupportedConfigs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
browseId = try values.decodeIfPresent(String.self, forKey: .browseId)
browseEndpointContextSupportedConfigs = try values.decodeIfPresent(BrowseEndpointContextSupportedConfigs.self, forKey: .browseEndpointContextSupportedConfigs)
}
struct BrowseEndpointContextSupportedConfigs: Codable {
let browseEndpointContextMusicConfig:BrowseEndpointContextMusicConfig?
enum CodingKeys: String, CodingKey {
case browseEndpointContextMusicConfig = "browseEndpointContextMusicConfig"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
browseEndpointContextMusicConfig = try values.decodeIfPresent(BrowseEndpointContextMusicConfig.self, forKey: .browseEndpointContextMusicConfig)
}
struct BrowseEndpointContextMusicConfig: Codable {
let pageType:String?
enum CodingKeys: String, CodingKey {
case pageType = "pageType"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
pageType = try values.decodeIfPresent(String.self, forKey: .pageType)
}
}
}
}
}
}
}
///
struct Subtitle: Codable {
let runs:[Run]?
enum CodingKeys: String, CodingKey {
case runs = "runs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
runs = try values.decodeIfPresent([Run].self, forKey: .runs)
}
struct Run: Codable {
///
let text:String?
enum CodingKeys: String, CodingKey {
case text = "text"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
text = try values.decodeIfPresent(String.self, forKey: .text)
}
}
}
///
struct Menu: Codable {
let menuRenderer:MenuRenderer?
enum CodingKeys: String, CodingKey {
case menuRenderer = "menuRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
menuRenderer = try values.decodeIfPresent(MenuRenderer.self, forKey: .menuRenderer)
}
struct MenuRenderer: Codable {
let items:[Item]?
enum CodingKeys: String, CodingKey {
case items = "items"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
items = try values.decodeIfPresent([Item].self, forKey: .items)
}
struct Item: Codable {
let menuNavigationItemRenderer:MenuNavigationItemRenderer?
enum CodingKeys: String, CodingKey {
case menuNavigationItemRenderer = "menuNavigationItemRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
menuNavigationItemRenderer = try values.decodeIfPresent(MenuNavigationItemRenderer.self, forKey: .menuNavigationItemRenderer)
}
struct MenuNavigationItemRenderer: Codable {
let navigationEndpoint:NavigationEndpoint?
enum CodingKeys: String, CodingKey {
case navigationEndpoint = "navigationEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
navigationEndpoint = try values.decodeIfPresent(NavigationEndpoint.self, forKey: .navigationEndpoint)
}
struct NavigationEndpoint: Codable {
let watchEndpoint:WatchEndpoint?
enum CodingKeys: String, CodingKey {
case watchEndpoint = "watchEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
watchEndpoint = try values.decodeIfPresent(WatchEndpoint.self, forKey: .watchEndpoint)
}
struct WatchEndpoint: Codable {
let playlistId:String?
enum CodingKeys: String, CodingKey {
case playlistId = "playlistId"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
playlistId = try values.decodeIfPresent(String.self, forKey: .playlistId)
}
}
}
}
}
}
}
///
struct Header: Codable {
let musicCardShelfHeaderBasicRenderer:MusicCardShelfHeaderBasicRenderer?
enum CodingKeys: String, CodingKey {
case musicCardShelfHeaderBasicRenderer = "musicCardShelfHeaderBasicRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicCardShelfHeaderBasicRenderer = try values.decodeIfPresent(MusicCardShelfHeaderBasicRenderer.self, forKey: .musicCardShelfHeaderBasicRenderer)
}
struct MusicCardShelfHeaderBasicRenderer: Codable {
let title:Title?
enum CodingKeys: String, CodingKey {
case title = "title"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
title = try values.decodeIfPresent(Title.self, forKey: .title)
}
struct Title:Codable {
let runs:[Run]?
enum CodingKeys: String, CodingKey {
case runs = "runs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
runs = try values.decodeIfPresent([Run].self, forKey: .runs)
}
struct Run: Codable {
///
let text:String?
enum CodingKeys: String, CodingKey {
case text = "text"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
text = try values.decodeIfPresent(String.self, forKey: .text)
}
}
}
}
}
///
struct Content: Codable {
let musicResponsiveListItemRenderer:RootMusicResponsiveListItemRenderer?
enum CodingKeys: String, CodingKey {
case musicResponsiveListItemRenderer = "musicResponsiveListItemRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicResponsiveListItemRenderer = try values.decodeIfPresent(RootMusicResponsiveListItemRenderer.self, forKey: .musicResponsiveListItemRenderer)
}
}
}
///
struct MusicShelfRenderer: Codable {
///
let title:Title?
///
let contents:[Content]?
///
let bottomEndpoint:BottomEndpoint?
enum CodingKeys: String, CodingKey {
case title = "title"
case contents = "contents"
case bottomEndpoint = "bottomEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
title = try values.decodeIfPresent(Title.self, forKey: .title)
contents = try values.decodeIfPresent([Content].self, forKey: .contents)
bottomEndpoint = try values.decodeIfPresent(BottomEndpoint.self, forKey: .bottomEndpoint)
}
struct Title:Codable {
let runs:[Run]?
enum CodingKeys: String, CodingKey {
case runs = "runs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
runs = try values.decodeIfPresent([Run].self, forKey: .runs)
}
struct Run: Codable {
///
let text:String?
enum CodingKeys: String, CodingKey {
case text = "text"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
text = try values.decodeIfPresent(String.self, forKey: .text)
}
}
}
struct Content: Codable {
let musicResponsiveListItemRenderer:RootMusicResponsiveListItemRenderer?
enum CodingKeys: String, CodingKey {
case musicResponsiveListItemRenderer = "musicResponsiveListItemRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicResponsiveListItemRenderer = try values.decodeIfPresent(RootMusicResponsiveListItemRenderer.self, forKey: .musicResponsiveListItemRenderer)
}
}
struct BottomEndpoint: Codable {
let searchEndpoint:SearchEndpoint?
enum CodingKeys: String, CodingKey {
case searchEndpoint = "searchEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
searchEndpoint = try values.decodeIfPresent(SearchEndpoint.self, forKey: .searchEndpoint)
}
struct SearchEndpoint: Codable {
///
let query:String?
///
let params:String?
enum CodingKeys: String, CodingKey {
case query = "query"
case params = "params"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
query = try values.decodeIfPresent(String.self, forKey: .query)
params = try values.decodeIfPresent(String.self, forKey: .params)
}
}
}
}
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,82 @@
//
// MPPositive_JsonSearchTypeContinuation.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/14.
//
import UIKit
///
struct JsonSearchTypeContinuation: Codable {
let continuationContents:ContinuationContents?
enum CodingKeys: String, CodingKey {
case continuationContents = "continuationContents"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
continuationContents = try values.decodeIfPresent(ContinuationContents.self, forKey: .continuationContents)
}
struct ContinuationContents: Codable {
let musicShelfContinuation:MusicShelfContinuation?
enum CodingKeys: String, CodingKey {
case musicShelfContinuation = "musicShelfContinuation"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicShelfContinuation = try values.decodeIfPresent(MusicShelfContinuation.self, forKey: .musicShelfContinuation)
}
struct MusicShelfContinuation: Codable {
///
let contents:[Content]?
///
let continuations:[Continuation]?
enum CodingKeys: String, CodingKey {
case contents = "contents"
case continuations = "continuations"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
contents = try values.decodeIfPresent([Content].self, forKey: .contents)
continuations = try values.decodeIfPresent([Continuation].self, forKey: .continuations)
}
///
struct Content: Codable {
let musicResponsiveListItemRenderer:RootMusicResponsiveListItemRenderer?
enum CodingKeys: String, CodingKey {
case musicResponsiveListItemRenderer = "musicResponsiveListItemRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicResponsiveListItemRenderer = try values.decodeIfPresent(RootMusicResponsiveListItemRenderer.self, forKey: .musicResponsiveListItemRenderer)
}
}
///
struct Continuation: Codable {
let nextContinuationData:NextContinuationData?
enum CodingKeys: String, CodingKey {
case nextContinuationData = "nextContinuationData"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
nextContinuationData = try values.decodeIfPresent(NextContinuationData.self, forKey: .nextContinuationData)
}
struct NextContinuationData: Codable {
///
let continuation:String?
///
let clickTrackingParams:String?
enum CodingKeys: String, CodingKey {
case continuation = "continuation"
case clickTrackingParams = "clickTrackingParams"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
continuation = try values.decodeIfPresent(String.self, forKey: .continuation)
clickTrackingParams = try values.decodeIfPresent(String.self, forKey: .clickTrackingParams)
}
}
}
}
}
}

View File

@ -0,0 +1,144 @@
//
// MPPositive_JsonSearchTypeResults.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/14.
//
import UIKit
///
struct JsonSearchTypeResults: Codable {
let contents:Contents?
enum CodingKeys: String, CodingKey {
case contents = "contents"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
contents = try values.decodeIfPresent(Contents.self, forKey: .contents)
}
struct Contents: Codable {
let tabbedSearchResultsRenderer:TabbedSearchResultsRenderer?
enum CodingKeys: String, CodingKey {
case tabbedSearchResultsRenderer = "tabbedSearchResultsRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
tabbedSearchResultsRenderer = try values.decodeIfPresent(TabbedSearchResultsRenderer.self, forKey: .tabbedSearchResultsRenderer)
}
struct TabbedSearchResultsRenderer: Codable {
let tabs:[Tab]?
enum CodingKeys: String, CodingKey {
case tabs = "tabs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
tabs = try values.decodeIfPresent([Tab].self, forKey: .tabs)
}
struct Tab:Codable {
let tabRenderer:TabRenderer?
enum CodingKeys: String, CodingKey {
case tabRenderer = "tabRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
tabRenderer = try values.decodeIfPresent(TabRenderer.self, forKey: .tabRenderer)
}
struct TabRenderer: Codable {
let content:Content?
enum CodingKeys: String, CodingKey {
case content = "content"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
content = try values.decodeIfPresent(Content.self, forKey: .content)
}
struct Content: Codable {
let sectionListRenderer:SectionListRenderer?
enum CodingKeys: String, CodingKey {
case sectionListRenderer = "sectionListRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
sectionListRenderer = try values.decodeIfPresent(SectionListRenderer.self, forKey: .sectionListRenderer)
}
struct SectionListRenderer: Codable {
///
let contents:[Content]?
enum CodingKeys: String, CodingKey {
case contents = "contents"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
contents = try values.decodeIfPresent([Content].self, forKey: .contents)
}
struct Content:Codable {
let musicShelfRenderer:MusicShelfRenderer?
enum CodingKeys: String, CodingKey {
case musicShelfRenderer = "musicShelfRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicShelfRenderer = try values.decodeIfPresent(MusicShelfRenderer.self, forKey: .musicShelfRenderer)
}
struct MusicShelfRenderer: Codable {
///
let contents:[Content]?
///
let continuations:[Continuation]?
enum CodingKeys: String, CodingKey {
case contents = "contents"
case continuations = "continuations"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
contents = try values.decodeIfPresent([Content].self, forKey: .contents)
continuations = try values.decodeIfPresent([Continuation].self, forKey: .continuations)
}
///
struct Content: Codable {
let musicResponsiveListItemRenderer:RootMusicResponsiveListItemRenderer?
enum CodingKeys: String, CodingKey {
case musicResponsiveListItemRenderer = "musicResponsiveListItemRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicResponsiveListItemRenderer = try values.decodeIfPresent(RootMusicResponsiveListItemRenderer.self, forKey: .musicResponsiveListItemRenderer)
}
}
///
struct Continuation: Codable {
let nextContinuationData:NextContinuationData?
enum CodingKeys: String, CodingKey {
case nextContinuationData = "nextContinuationData"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
nextContinuationData = try values.decodeIfPresent(NextContinuationData.self, forKey: .nextContinuationData)
}
struct NextContinuationData: Codable {
///
let continuation:String?
///
let clickTrackingParams:String?
enum CodingKeys: String, CodingKey {
case continuation = "continuation"
case clickTrackingParams = "clickTrackingParams"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
continuation = try values.decodeIfPresent(String.self, forKey: .continuation)
clickTrackingParams = try values.decodeIfPresent(String.self, forKey: .clickTrackingParams)
}
}
}
}
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,21 @@
//
// MPPositive_ArtistHeaderModel.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/15.
//
import UIKit
///
class MPPositive_ArtistHeaderModel: NSObject {
///
var title:String?
///
var thumbnails:[String]?
///
var subscriptions:String?
///
var subscriptionedText:String?
///
var fordescription:String?
}

View File

@ -8,37 +8,27 @@
import UIKit
///b
class MPPositive_BrowseItemModel: NSObject {
///
var coverUrl:String!
///
var coverUrls:[String]!
///
var maintitle:String!
var title:String!
///
var subtitle:String!
/////
var thirdtitle:String!
///item
var musicVideo:BrowseItemMusicVideo!
///item
var browseContent:BrowseItemContent!
///ID
var videoId:String?
///ID
var playListId:String?
///ID
var browseId:String?
///
var params:String?
///Id
var artistId:String?
///
var pageType:String?
////OR/
var itemType:BrowseItemType!
}
struct BrowseItemMusicVideo {
///ID
var videoId:String?
///ID
var playListId:String?
///
var musicVideoType:String?
}
struct BrowseItemContent{
///ID
var browseId:String?
///
var params:String?
///
var pageType:String?
}
///
enum BrowseItemType: Int{
////

View File

@ -7,17 +7,17 @@
import UIKit
///
class MPPositive_SearchResultItemModel: NSObject {
///
var reviewUrls:[String]?
///
var title:String?
///
var subtitle:String?
////VideoID
var videoId:String?
///ID//
var browseId:String?
///
var itemType:BrowseItemType?
}
//class MPPositive_SearchResultItemModel: NSObject {
// ///
// var reviewUrls:[String]?
// ///
// var title:String?
// ///
// var subtitle:String?
// ////VideoID
// var videoId:String?
// ///ID//
// var browseId:String?
// ///
// var itemType:BrowseItemType?
//}

View File

@ -10,8 +10,10 @@ import UIKit
class MPPositive_SongItemModel: NSObject {
///
var index:Int!
///
///
var resourceUrls:[String]?
///
var audioUrls:[String]?
///
var coverUrls:[String]?
///

View File

@ -0,0 +1,73 @@
//
// MPPositive_ArtistContentListViewModel.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/15.
//
import UIKit
class MPPositive_ArtistContentListViewModel: NSObject {
///
var title:String?
///
var itemViews:[MPPositive_BrowseItemViewModel] = []{
didSet{
print("用户加载\(title ?? "")的新内容")
if completionBlock != nil {
completionBlock!()
}
}
}
///id
var browseId:String?
///
var params:String?
///
var continuation:String?
///
var itct:String?
///
var completionBlock:(() -> Void)?
init(_ title: String?, itemViews: [MPPositive_BrowseItemViewModel], browseId: String?, params:String?) {
super.init()
self.title = title
self.itemViews = itemViews
self.browseId = browseId
self.params = params
}
///
func requestArtistType() {
//moreActionIdmoreActionParams
guard let id = self.browseId, let params = params else {
print("当前\(title ?? "")已是全部内容")
return
}
//
itemViews = []
//
MP_NetWorkManager.shared.requestArtistMore(id, params: params) { [weak self] (items, continuation, itct) in
guard let self = self else {return}
//
itemViews.append(contentsOf: items)
self.continuation = continuation
self.itct = itct
}
}
///
func requestArtistContinuation() {
guard let continuation = self.continuation, let itct = self.itct else {
//
return
}
MP_NetWorkManager.shared.requestArtistMoreContinuation(continuation, itct: itct) { [weak self] (items, continuation, itct) in
guard let self = self else {return}
//
itemViews.append(contentsOf: items)
self.continuation = continuation
self.itct = itct
}
}
}

View File

@ -0,0 +1,15 @@
//
// MPPositive_ArtistViewModel.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/15.
//
import UIKit
class MPPositive_ArtistViewModel: NSObject {
///
var header:MPPositive_ArtistHeaderModel!
///
var lists:[MPPositive_ArtistContentListViewModel] = []
}

View File

@ -11,9 +11,9 @@ import Kingfisher
class MPPositive_BrowseItemViewModel: NSObject {
///
var coverUrl:URL?
///
///
var title:String!
///
///
var subtitle:String!
///
var browseItem:MPPositive_BrowseItemModel!
@ -25,19 +25,16 @@ class MPPositive_BrowseItemViewModel: NSObject {
//ui
private func confirgue (_ browseItem:MPPositive_BrowseItemModel) {
//
if let url = URL(string: browseItem.coverUrl ?? "") {
if let url = URL(string: browseItem.coverUrls?.last ?? "") {
self.coverUrl = url
}else {
print("No Cover")
}
//
title = browseItem.maintitle
title = browseItem.title
if browseItem.subtitle != nil {
subtitle = browseItem.subtitle
}
if browseItem.thirdtitle != nil {
subtitle = (subtitle ?? "")+"·"+browseItem.thirdtitle
}
}
///
func setUrltoImage(_ imageView:UIImageView) {

View File

@ -12,4 +12,6 @@ class MPPositive_ListAlbumListViewModel: NSObject {
var header:MPPositive_ListHeaderViewModel!
///
var items:[MPPositive_BrowseItemViewModel] = []
}

View File

@ -0,0 +1,15 @@
//
// MPPositive_RecommendListViewModel.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/20.
//
import UIKit
class MPPositive_RecommendListViewModel: NSObject {
///
var title:String?
///
var items:[MPPositive_BrowseItemViewModel] = []
}

View File

@ -15,14 +15,14 @@ class MPPositive_SearchResultItemViewModel: NSObject {
///
var subtitle:String?
///
var item:MPPositive_SearchResultItemModel!
init(_ resultItem:MPPositive_SearchResultItemModel) {
var item:MPPositive_BrowseItemModel!
init(_ resultItem:MPPositive_BrowseItemModel) {
super.init()
item = resultItem
configure()
}
private func configure() {
if let url = URL(string: item.reviewUrls?.last ?? "") {
if let url = URL(string: item.coverUrls?.last ?? "") {
reviewUrl = url
}
if item.title != nil {

View File

@ -34,7 +34,7 @@ class MPPositive_SearchResultListViewModel: NSObject {
///
func requestSearchType() {
guard let query = self.query, let params = self.params else {
print("当前模块已是全部内容")
print("当前\(title ?? "")已是全部内容")
return
}
//
@ -47,4 +47,19 @@ class MPPositive_SearchResultListViewModel: NSObject {
self.itct = itct
}
}
///
func requestSearchContinuation() {
guard let continuation = self.continuation, let itct = self.itct else {
//
return
}
MP_NetWorkManager.shared.requestSearchTypeContinuation(continuation, itct: itct) { [weak self] (items, continuation, itct) in
guard let self = self else {return}
//
allItemViews.append(contentsOf: items)
//
self.continuation = continuation
self.itct = itct
}
}
}

View File

@ -13,6 +13,8 @@ class MPPositive_SongViewModel: NSObject {
var index:Int!
///
var resourcePlayerItem:AVPlayerItem!
///
var resourceAsset:AVURLAsset!
///
var coverUrl:URL?
///
@ -23,38 +25,35 @@ class MPPositive_SongViewModel: NSObject {
var lyrics:String?
///ID
var relatedId:String?
///
var isPreloading:Bool?
///
var isCollection:Bool?
///
var isDlownd:Bool?
///
var song:MPPositive_SongItemModel!
///KVO
var isKvo:Bool = false
///
var isPloading:Bool = false
init(_ song:MPPositive_SongItemModel) {
super.init()
self.song = song
configure()
}
deinit {
if isKvo == true {
resourcePlayerItem.removeObserver(self, forKeyPath: "playbackLikelyToKeepUp")
isKvo = false
}
}
//
private func configure() {
index = song.index
//
if song.resourceUrls?.first != nil {
let first = URL(string: song.resourceUrls!.first!)
resourcePlayerItem = .init(url: first!)
if song.resourceUrls?.first != nil, let first = URL(string: song.resourceUrls?.first ?? ""){
//
resourceAsset = .init(url: first, options: [AVURLAssetPreferPreciseDurationAndTimingKey: true])
//PlayerItem
resourcePlayerItem = .init(asset: resourceAsset)
}
//
if song.coverUrls?.first != nil {
coverUrl = .init(string: song.coverUrls!.last!)
if song.reviewUrls?.first != nil {
coverUrl = .init(string: song.reviewUrls!.last!)
}
//
if song.title != nil {
@ -79,45 +78,60 @@ class MPPositive_SongViewModel: NSObject {
//
}
//MARK: -
//
private func canBePreloaded() -> Bool {
return self.isPreloading ?? false
}
///
func preloadPlayerItem() {
//
guard canBePreloaded() == false else {
print("\(title ?? "")已经预加载了")
return
if isPloading == false {
preloadAsset(resourceAsset)
}
print(resourcePlayerItem.status)
//
self.resourcePlayerItem.seek(to: .zero) {[weak self] _ in
}
//
func preloadAsset(_ asset:AVURLAsset) {
//
if #available(iOS 16, *) {
//ios16
Task{
do{
let playable = try await asset.load(.isPlayable)
if playable == true {
print("\(self.title ?? "")预加载成功")
self.isPloading = true
}else {
//
switch asset.status(of: .isPlayable) {
case .failed(let erro):
print("\(title ?? "")预加载失败,失败原因:\(erro.localizedDescription)")
default:
break
}
}
}catch{
print("预加载失败:\(error.localizedDescription)")
}
}
}else {
//ios16
let keys = ["playable"]
asset.loadValuesAsynchronously(forKeys: keys) {
[weak self] in
guard let self = self else {return}
//
DispatchQueue.global(qos: .background).async {
if self.isKvo == false {
//playerItem
self.resourcePlayerItem.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: .new, context: nil)
self.isKvo = true
for key in keys {
var error: NSError? = nil
let status = asset.statusOfValue(forKey: key, error: &error)
switch status {
case .loaded:
// key
DispatchQueue.main.async {
print("\(self.title ?? "")预加载成功")
self.isPloading = true
}
case .failed:
print("\(title ?? "")预加载失败,失败原因:\(error?.localizedDescription ?? "")")
case .cancelled:
print("\(title ?? "")预加载被取消了")
default:
break
}
}
}
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "playbackLikelyToKeepUp" {
if let playbackLikelyToKeepUp = change?[.newKey] as? Bool, playbackLikelyToKeepUp == true {
//KVO
print("\(title ?? "")预加载到合适的进度")
if isKvo == true {
resourcePlayerItem.removeObserver(self, forKeyPath: "playbackLikelyToKeepUp")
isKvo = false
}
//
isPreloading = true
}
}
}
}

View File

@ -10,8 +10,6 @@ import UIKit
class MPPositive_BrowseLoadViewModel: NSObject {
///()
var browseModuleLists:[MPPositive_BrowseModuleListViewModel] = []
///
var listAlbumList:MPPositive_ListAlbumListViewModel!
override init() {
super.init()
//browse
@ -25,14 +23,6 @@ class MPPositive_BrowseLoadViewModel: NSObject {
//UI
NotificationCenter.notificationKey.post(notificationName: .positive_browses_reload)
}
//
MP_NetWorkManager.shared.listRequestResultBlock = {
[weak self] (list) in
guard let self = self else {return}
listAlbumList = list
//UI
NotificationCenter.notificationKey.post(notificationName: .positive_list_reload)
}
}
///
func reloadBrowseLists() {
@ -41,11 +31,4 @@ class MPPositive_BrowseLoadViewModel: NSObject {
//
MP_NetWorkManager.shared.requestBrowseDatas()
}
///
func requestListOrAlbum(_ item:MPPositive_BrowseItemViewModel) {
//
listAlbumList = nil
//
MP_NetWorkManager.shared.requestAlbumOrListDatas(item)
}
}

View File

@ -68,7 +68,6 @@ class MPPositive_PlayerLoadViewModel: NSObject {
//videoID,
array = array.filter({!videoIDs.contains($0.videoId)})
group = DispatchGroup()
var numbers = 0
//,
for item in array {
group?.enter()
@ -81,7 +80,8 @@ class MPPositive_PlayerLoadViewModel: NSObject {
group?.enter()
//
improveDataforResouceAndCover(item) {[weak self] resourceUrls, coverUrls in
item.resourceUrls = resourceUrls
item.resourceUrls = resourceUrls.1
item.audioUrls = resourceUrls.0
item.coverUrls = coverUrls
//,ViewModellistViewVideos
self?.listViewVideos.append(.init(item))
@ -96,6 +96,28 @@ class MPPositive_PlayerLoadViewModel: NSObject {
self.group = nil
})
}
///
func remakeImproveData(_ completion:@escaping (() -> Void)) {
//
improveDataforResouceAndCover(currentVideo.song) {[weak self] resourceUrls, coverUrls in
guard let self = self else {return}
currentVideo.song.resourceUrls = resourceUrls.1
currentVideo.song.audioUrls = resourceUrls.0
//listViewVideos
listViewVideos.forEach({ item in
if item.song.videoId == self.currentVideo.song.videoId {
item.song.resourceUrls = self.currentVideo.song.resourceUrls
item.song.audioUrls = self.currentVideo.song.audioUrls
}
})
currentVideo.resourceAsset = .init(url: .init(string: currentVideo.song.resourceUrls!.first!)!)
currentVideo.resourcePlayerItem = .init(asset: currentVideo.resourceAsset!)
//UI
NotificationCenter.notificationKey.post(notificationName: .positive_player_reload)
completion()
}
}
///songlistViewVideosindex
func removeData(_ targetVideoId:String) {
let targetIndex = songVideos.firstIndex(where: {$0.videoId == targetVideoId}) ?? 0
@ -126,7 +148,7 @@ class MPPositive_PlayerLoadViewModel: NSObject {
}
}
///player
private func improveDataforResouceAndCover(_ song:MPPositive_SongItemModel, completion:@escaping(([String]?, [String]?) -> Void)) {
private func improveDataforResouceAndCover(_ song:MPPositive_SongItemModel, completion:@escaping((([String],[String]), [String]?) -> Void)) {
//player
MP_NetWorkManager.shared.requestPlayer(song) { resourceUrls, coverUrls in
completion(resourceUrls,coverUrls)

View File

@ -0,0 +1,19 @@
//
// MPPositive_RecommendLoadViewModel.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/20.
//
import UIKit
///
class MPPositive_RecommendLoadViewModel: NSObject {
///
var sectionLists:[MPPositive_RecommendListViewModel]!
///
/// - Parameter browseId: Id
init(_ browseId:String) {
super.init()
}
}

View File

@ -7,7 +7,7 @@
import UIKit
///
class MPPositive_LoadSearchResultsViewModel: NSObject {
class MPPositive_SearchResultsLoadViewModel: NSObject {
///
var sectionLists:[MPPositive_SearchResultListViewModel]!{
didSet{

View File

@ -0,0 +1,194 @@
//
// MPPositive_ArtistShowViewController.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/15.
//
import UIKit
class MPPositive_ArtistShowViewController: MPPositive_BaseViewController {
///
private lazy var headView:MPPositive_ArtistShowHeaderView = .init(frame: .init(x: 0, y: 0, width: screen_Width, height: 385*width))
///Label
private lazy var nameLabel:UILabel = {
let label:UILabel = createLabel(font: .systemFont(ofSize: 20*width, weight: .regular), textColor: .white, textAlignment: .left)
label.alpha = 0
return label
}()
//MARK: -
//View
private lazy var segmentView:JXSegmentedView = {
var jxSegmentView = JXSegmentedView()
jxSegmentView.backgroundColor = .init(hex: "1A1A1A")
return jxSegmentView
}()
//
private lazy var dataSource:JXSegmentedTitleDataSource = {
var dataSource = JXSegmentedTitleDataSource()
//
dataSource.titleNormalColor = .init(hex: "#666666")
dataSource.titleNormalFont = .systemFont(ofSize: 16*width, weight: .regular)
//
dataSource.titleSelectedColor = .init(hex: "#80F988")
dataSource.titleSelectedFont = .systemFont(ofSize: 16*width, weight: .bold)
//
dataSource.isTitleColorGradientEnabled = true
dataSource.isSelectedAnimable = true
dataSource.isItemTransitionEnabled = true
return dataSource
}()
//
private lazy var indicator:JXSegmentedIndicatorLineView = {
let indicator = JXSegmentedIndicatorLineView()
indicator.indicatorWidth = 16*width
indicator.indicatorHeight = 3*width
indicator.indicatorCornerRadius = 1.5*width
indicator.indicatorColor = .init(hex: "#80F988")
indicator.indicatorPosition = .bottom
indicator.verticalOffset = 5*width
return indicator
}()
///
fileprivate lazy var pagingView: JXPagingView = {
let pagingView = JXPagingView(delegate: self)
pagingView.backgroundColor = .clear
pagingView.isHidden = true
pagingView.mainTableView.backgroundColor = .clear
//
pagingView.mainTableView.bounces = false
return pagingView
}()
private var artist:MPPositive_ArtistViewModel!{
didSet{
if artist != nil {
pagingView.isHidden = false
//
headView.artist = artist
//
dataSource.titles = artist.lists.compactMap({$0.title})
nameLabel.text = artist.header.title
dataSource.reloadData(selectedIndex: 0)
segmentView.reloadData()
}
}
}
///
/// - Parameter browseId: Id
init(_ browseId:String) {
super.init(nibName: nil, bundle: nil)
//
MP_NetWorkManager.shared.requestArtist(browseId) { [weak self] result in
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
artist = result
}
}
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func viewDidLoad() {
super.viewDidLoad()
setPopBtn()
setTitle("")
configure()
}
private func configure() {
segmentView.dataSource = dataSource
segmentView.indicators = [indicator]
view.addSubview(pagingView)
pagingView.snp.makeConstraints { make in
make.top.equalToSuperview()
make.left.right.bottom.equalToSuperview().priority(999)
}
segmentView.listContainer = pagingView.listContainerView
pagingView.listContainerView.backgroundColor = .clear
pagingView.listContainerView.listCellBackgroundColor = .clear
navView.addSubview(nameLabel)
nameLabel.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.left.equalToSuperview().offset(68*width)
make.right.equalToSuperview().offset(-16*width)
}
pagingView.pinSectionHeaderVerticalOffset = Int(statusBarHeight + (50*width))
}
}
//MARK: - JXPagingViewDelegate
extension MPPositive_ArtistShowViewController: JXPagingViewDelegate{
func pagingView(_ pagingView: JXPagingView, mainTableViewDidScroll scrollView: UIScrollView) {
//
let allHeight = statusBarHeight + (50*width)
let maxHeight = headView.frame.height - allHeight
let currentOffset = scrollView.contentOffset
if currentOffset.y <= maxHeight {
//header
let value:CGFloat = currentOffset.y/maxHeight
let topValue:CGFloat = 1-value
if value >= 0.8 {
nameLabel.alpha = 1
headView.alpha = 0
}else {
nameLabel.alpha = value
headView.alpha = topValue
}
}
}
//
func tableHeaderViewHeight(in pagingView: JXPagingView) -> Int {
return Int(headView.frame.height)
}
//View
func tableHeaderView(in pagingView: JXPagingView) -> UIView {
return headView
}
//
func heightForPinSectionHeader(in pagingView: JXPagingView) -> Int {
return Int(60 * width)
}
//
func viewForPinSectionHeader(in pagingView: JXPagingView) -> UIView {
return segmentView
}
//
func numberOfLists(in pagingView: JXPagingView) -> Int {
return dataSource.titles.count
}
//View
func pagingView(_ pagingView: JXPagingView, initListAtIndex index: Int) -> JXPagingViewListViewDelegate {
let showView:MPPositive_ArtistShowTypeView = .init(frame: .init(x: 0, y: 0, width: screen_Width, height: screen_Height-headView.frame.height-(60 * width)), list: artist.lists[index])
showView.header = artist.header
showView.chooseItemBlock = {
[weak self] (item) in
guard let self = self else {return}
switch item.browseItem.itemType {
case .artist:
//
let artistVC = MPPositive_ArtistShowViewController(item.browseItem.browseId ?? "")
navigationController?.pushViewController(artistVC, animated: true)
case .list:
//
let listVC = MPPositive_ListShowViewController(item.browseItem.browseId ?? "", params: "")
navigationController?.pushViewController(listVC, animated: true)
case .single:
///
//next
MP_NetWorkManager.shared.requestNextList(item.browseItem.playListId ?? "", videoId: item.browseItem.videoId ?? ""){ [weak self] listSongs in
guard let self = self else {return}
//playerloadViewModel
let lodaViewModel = MPPositive_PlayerLoadViewModel(listSongs, currentVideoId: item.browseItem.videoId ?? "")
lodaViewModel.improveData(item.browseItem.videoId ?? "")
MP_PlayerManager.shared.loadPlayer = lodaViewModel
NotificationCenter.notificationKey.post(notificationName: .pup_player_vc)
}
case .none:
break
}
}
return showView
}
}

View File

@ -43,11 +43,6 @@ class MPPositive_HomeViewController: MPPositive_BaseViewController{
//
loadViewModel.reloadBrowseLists()
NotificationCenter.notificationKey.add(observer: self, selector: #selector(reloadAction(_ :)), notificationName: .positive_browses_reload)
NotificationCenter.notificationKey.add(observer: self, selector: #selector(listAction(_ :)), notificationName: .positive_list_reload)
// let item = MPPositive_DownloadItemModel.create()
// item.resourcePath = "11"
// MPPositive_DownloadItemModel.save()
}
deinit {
NotificationCenter.default.removeObserver(self)
@ -81,17 +76,10 @@ class MPPositive_HomeViewController: MPPositive_BaseViewController{
self?.tableView.reloadData()
}
}
//
@objc private func listAction(_ sender:Notification) {
let listVC = MPPositive_ListShowViewController()
listVC.listOrAlbum = loadViewModel.listAlbumList
navigationController?.pushViewController(listVC, animated: true)
}
//MARK: -
//
@objc private func menuRightClick(_ sender:UIButton) {
// let array = MPPositive_DownloadItemModel.fetchAll()
// print(array)
}
}
//MARK: - tableView
@ -113,17 +101,18 @@ extension MPPositive_HomeViewController: UITableViewDataSource, UITableViewDeleg
case .single:
///
//next
MP_NetWorkManager.shared.requestNextList(item){ [weak self] listSongs in
MP_NetWorkManager.shared.requestNextList(item.browseItem.playListId ?? "", videoId: item.browseItem.videoId ?? ""){ [weak self] listSongs in
guard let self = self else {return}
//playerloadViewModel
let lodaViewModel = MPPositive_PlayerLoadViewModel(listSongs, currentVideoId: item.browseItem.musicVideo.videoId ?? "")
lodaViewModel.improveData(item.browseItem.musicVideo.videoId ?? "")
let lodaViewModel = MPPositive_PlayerLoadViewModel(listSongs, currentVideoId: item.browseItem.videoId ?? "")
lodaViewModel.improveData(item.browseItem.videoId ?? "")
MP_PlayerManager.shared.loadPlayer = lodaViewModel
NotificationCenter.notificationKey.post(notificationName: .pup_player_vc)
}
case .list:
///
loadViewModel.requestListOrAlbum(item)
//
let listVC = MPPositive_ListShowViewController(item.browseItem.browseId ?? "", params: item.browseItem.params ?? "")
navigationController?.pushViewController(listVC, animated: true)
default:
break
}

View File

@ -68,17 +68,43 @@ class MPPositive_ListShowViewController: MPPositive_BaseViewController {
}()
private let MPPositive_MusicItemShowTableViewCellID = "MPPositive_MusicItemShowTableViewCell"
///
var listOrAlbum:MPPositive_ListAlbumListViewModel!
private var listOrAlbum:MPPositive_ListAlbumListViewModel!{
didSet{
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
if listOrAlbum != nil {
reload()
tableView.reloadData()
}
}
}
}
///
/// - Parameters:
/// - browseId: id
/// - params:
init(_ browseId:String, params:String) {
super.init(nibName: nil, bundle: nil)
//
MP_NetWorkManager.shared.requestAlbumOrListDatas(browseId, params: params) { [weak self] result in
guard let self = self else {return}
listOrAlbum = result
}
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func viewDidLoad() {
super.viewDidLoad()
setTitle("")
setPopBtn()
configure()
// MP_NetWorkManager.shared.testFunction()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
reload()
}
//
private func reload() {
@ -206,7 +232,7 @@ class MPPositive_ListShowViewController: MPPositive_BaseViewController {
//MARK: - tableView
extension MPPositive_ListShowViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return listOrAlbum.items.count
return listOrAlbum?.items.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_MusicItemShowTableViewCellID, for: indexPath) as! MPPositive_MusicItemShowTableViewCell
@ -215,11 +241,11 @@ extension MPPositive_ListShowViewController: UITableViewDataSource, UITableViewD
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//next
MP_NetWorkManager.shared.requestNextList(listOrAlbum.items[indexPath.row]){ [weak self] listSongs in
MP_NetWorkManager.shared.requestNextList(listOrAlbum.items[indexPath.row].browseItem.playListId ?? "", videoId: listOrAlbum.items[indexPath.row].browseItem.videoId ?? ""){ [weak self] listSongs in
guard let self = self else {return}
//playerloadViewModel
let lodaViewModel = MPPositive_PlayerLoadViewModel(listSongs, currentVideoId: listOrAlbum.items[indexPath.row].browseItem.musicVideo.videoId ?? "")
lodaViewModel.improveData(listOrAlbum.items[indexPath.row].browseItem.musicVideo.videoId ?? "")
let lodaViewModel = MPPositive_PlayerLoadViewModel(listSongs, currentVideoId: listOrAlbum.items[indexPath.row].browseItem.videoId ?? "")
lodaViewModel.improveData(listOrAlbum.items[indexPath.row].browseItem.videoId ?? "")
MP_PlayerManager.shared.loadPlayer = lodaViewModel
//
NotificationCenter.notificationKey.post(notificationName: .pup_player_vc)

View File

@ -63,7 +63,7 @@ class MPPositive_PlayerViewController: MPPositive_BaseViewController, UIViewCont
let blurEffect = UIBlurEffect(style: .dark)
//
let blurEffectView = UIVisualEffectView(effect: blurEffect)
blurEffectView.alpha = 0.8
blurEffectView.alpha = 1
blurEffectView.isUserInteractionEnabled = false
return blurEffectView
}()
@ -131,6 +131,18 @@ class MPPositive_PlayerViewController: MPPositive_BaseViewController, UIViewCont
let value = currentTime/duration
coverView.sliderView.value = Float(value)
}
//
MP_PlayerManager.shared.cacheValueBlock = { [weak self] (value, duration) in
guard let self = self else { return }
if value < duration {
//
let float = value/duration
coverView.progressView.setProgress(Float(float), animated: false)
}else {
//
coverView.progressView.setProgress(1, animated: false)
}
}
switch MP_PlayerManager.shared.getPlayState() {
case .Null://
playBtn.isSelected = false

View File

@ -0,0 +1,17 @@
//
// MPPositive_RecommendViewController.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/20.
//
import UIKit
///
class MPPositive_RecommendViewController: MPPositive_BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
setTitle("Recommend")
setPopBtn()
}
}

View File

@ -78,6 +78,33 @@ class MPPositive_SearchResultShowViewController: MPPositive_BaseViewController {
[weak self] in
self?.view?.endEditing(true)
}
resultsShowView.chooseItemBlock = {
[weak self] (item) in
guard let self = self else {return}
switch item.item.itemType {
case .artist:
//
let artistVC = MPPositive_ArtistShowViewController(item.item.browseId ?? "")
navigationController?.pushViewController(artistVC, animated: true)
case .list:
//
let listVC = MPPositive_ListShowViewController(item.item.browseId ?? "", params: "")
navigationController?.pushViewController(listVC, animated: true)
case .single:
///
//next
MP_NetWorkManager.shared.requestNextList(item.item.playListId ?? "", videoId: item.item.videoId ?? ""){ [weak self] listSongs in
guard let self = self else {return}
//playerloadViewModel
let lodaViewModel = MPPositive_PlayerLoadViewModel(listSongs, currentVideoId: item.item.videoId ?? "")
lodaViewModel.improveData(item.item.videoId ?? "")
MP_PlayerManager.shared.loadPlayer = lodaViewModel
NotificationCenter.notificationKey.post(notificationName: .pup_player_vc)
}
case .none:
break
}
}
}
deinit{
debounceTimer = nil

View File

@ -0,0 +1,43 @@
//
// MPPositive_ArtistDescriptionTableViewCell.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/16.
//
import UIKit
class MPPositive_ArtistDescriptionTableViewCell: UITableViewCell {
lazy var titleLabel:UILabel = createLabel(font: .systemFont(ofSize: 14*width, weight: .regular), textColor: .white, textAlignment: .left, lines: 0)
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
backgroundColor = .clear
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
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
}
private func configure() {
contentView.addSubview(titleLabel)
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(18*width)
make.right.equalToSuperview().offset(-18*width)
make.top.equalToSuperview().offset(10*width).priority(999)
make.bottom.equalToSuperview().offset(10*width).priority(999)
}
}
}

View File

@ -0,0 +1,56 @@
//
// MPPositive_ArtistShowCollectionViewCell.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/16.
//
import UIKit
///cell
class MPPositive_ArtistShowCollectionViewCell: UICollectionViewCell {
///
private lazy var avatarImageView:UIImageView = {
let imageView:UIImageView = .init()
imageView.contentMode = .scaleAspectFill
imageView.layer.masksToBounds = true
imageView.layer.cornerRadius = 82*width
return imageView
}()
///
private lazy var titleLabel:UILabel = createLabel("Title", font: .systemFont(ofSize: 14*width, weight: .regular), textColor: .white, textAlignment: .center)
///
private lazy var subtitleLabel:UILabel = createLabel("Title", font: .systemFont(ofSize: 12*width, weight: .regular), textColor: .init(hex: "#FFFFFF", alpha: 0.6), textAlignment: .center)
var itemView:MPPositive_BrowseItemViewModel!{
didSet{
itemView.setUrltoImage(avatarImageView)
titleLabel.text = itemView.title
subtitleLabel.text = itemView.subtitle
}
}
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .clear
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
private func configure() {
addSubview(avatarImageView)
avatarImageView.snp.makeConstraints { make in
make.left.top.right.equalToSuperview()
make.height.equalTo(164*width)
}
addSubview(titleLabel)
titleLabel.snp.makeConstraints { make in
make.left.right.equalToSuperview()
make.top.equalTo(avatarImageView.snp.bottom).offset(16*width)
}
addSubview(subtitleLabel)
subtitleLabel.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
}
}
}

View File

@ -0,0 +1,86 @@
//
// MPPositive_ArtistShowHeaderView.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/15.
//
import UIKit
import Kingfisher
class MPPositive_ArtistShowHeaderView: UIView {
//
private lazy var reviewImageView:UIImageView = {
let imageView:UIImageView = .init()
imageView.contentMode = .scaleAspectFill
return imageView
}()
//
private lazy var nameLabel:UILabel = createLabel(font: .systemFont(ofSize: 32*width, weight: .semibold), textColor: .white, textAlignment: .left)
//label
private lazy var followersLabel:UILabel = createLabel(font: .systemFont(ofSize: 14*width, weight: .regular), textColor: .white, textAlignment: .left)
//
private lazy var collectionBtn:UIButton = {
let btn = UIButton()
btn.setImage(UIImage(named: "Artist_Collection'logo"), for: .normal)
btn.setImage(UIImage(named: "Artist_Collectioned'logo"), for: .selected)
btn.setBackgroundImage(UIImage(named: "Artist_Collection'bg"), for: .normal)
btn.setBackgroundImage(UIImage(named: "Artist_Collectioned'bg"), for: .selected)
btn.addTarget(self, action: #selector(collectionClick(_ :)), for: .touchUpInside)
return btn
}()
//
private lazy var maskImageView:UIImageView = {
let imageView:UIImageView = .init(image: .init(named: "List_Cover'mask"))
imageView.contentMode = .scaleAspectFill
imageView.isUserInteractionEnabled = false
return imageView
}()
var artist:MPPositive_ArtistViewModel!{
didSet{
reviewImageView.kf.setImage(with: URL(string: artist.header.thumbnails?.last ?? ""), placeholder: placeholderImage)
nameLabel.text = artist.header.title
followersLabel.text = (artist.header.subscriptions ?? "")+" "+(artist.header.subscriptionedText ?? "")
}
}
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .clear
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
private func configure() {
addSubview(reviewImageView)
reviewImageView.snp.makeConstraints { make in
make.left.right.top.bottom.equalToSuperview()
}
addSubview(maskImageView)
maskImageView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.height.equalTo(225*width)
}
addSubview(collectionBtn)
collectionBtn.snp.makeConstraints { make in
make.right.equalToSuperview().offset(-18*width).priority(999)
make.bottom.equalToSuperview().offset(-50*width).priority(999)
make.width.height.equalTo(72*width)
}
addSubview(nameLabel)
nameLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(18*width).priority(999)
make.bottom.equalToSuperview().offset(-80*width).priority(999)
make.right.equalTo(collectionBtn.snp.left).offset(-10*width).priority(999)
}
addSubview(followersLabel)
followersLabel.snp.makeConstraints { make in
make.left.right.equalTo(nameLabel)
make.bottom.equalTo(collectionBtn)
}
}
//
@objc private func collectionClick(_ sender:UIButton) {
}
}

View File

@ -0,0 +1,55 @@
//
// MPPositive_ArtistShowListCollectionViewCell.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/16.
//
import UIKit
class MPPositive_ArtistShowListCollectionViewCell: UICollectionViewCell {
//
private lazy var coverImageView:UIImageView = {
let imageView:UIImageView = .init()
imageView.contentMode = .scaleAspectFill
imageView.layer.masksToBounds = true
imageView.layer.cornerRadius = 16*width
return imageView
}()
//Label
private lazy var titleLabel:UILabel = createLabel("Title", font: .systemFont(ofSize: 14*width, weight: .regular), textColor: .white, textAlignment: .left)
//
private lazy var subtitleLabel:UILabel = createLabel("Title", font: .systemFont(ofSize: 12*width, weight: .regular), textColor: .init(hex: "#FFFFFF", alpha: 0.6), textAlignment: .left)
var itemView:MPPositive_BrowseItemViewModel!{
didSet{
itemView.setUrltoImage(coverImageView)
titleLabel.text = itemView.title
subtitleLabel.text = itemView.subtitle
}
}
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .clear
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
private func configure() {
addSubview(coverImageView)
coverImageView.snp.makeConstraints { make in
make.left.top.right.equalToSuperview()
make.height.equalTo(167*width)
}
addSubview(titleLabel)
titleLabel.snp.makeConstraints { make in
make.left.right.equalToSuperview()
make.top.equalTo(coverImageView.snp.bottom).offset(16*width)
}
addSubview(subtitleLabel)
subtitleLabel.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
}
}
}

View File

@ -0,0 +1,123 @@
//
// MPPositive_ArtistShowListableViewCell.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/16.
//
import UIKit
class MPPositive_ArtistShowListableViewCell: UITableViewCell {
///Layout
enum ArtistType:Int {
case List = 0
case Artist = 1
var layout:UICollectionViewFlowLayout {
let layout = UICollectionViewFlowLayout()
layout.itemSize = .init(width: 164*width, height: 223*width)
switch self {
case .List:
layout.sectionInset = .init(top: 15*width, left: 18*width, bottom: 40*width, right: 18*width)
layout.minimumInteritemSpacing = 10*width
layout.minimumLineSpacing = 16*width
layout.scrollDirection = .vertical
case .Artist:
layout.sectionInset = .init(top: 15*width, left: 18*width, bottom: 40*width, right: 18*width)
layout.minimumLineSpacing = 10*width
layout.scrollDirection = .horizontal
}
return layout
}
}
//CollectionViewCell
private lazy var collectionView:UICollectionView = {
let collectionView:UICollectionView = .init(frame: .init(x: 0, y: 0, width: screen_Width, height: screen_Height), collectionViewLayout: showType.layout)
collectionView.showsVerticalScrollIndicator = false
collectionView.showsHorizontalScrollIndicator = false
collectionView.backgroundColor = .clear
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(MPPositive_ArtistShowListCollectionViewCell.self, forCellWithReuseIdentifier: MPPositive_ArtistShowListCollectionViewCellID)
collectionView.register(MPPositive_ArtistShowCollectionViewCell.self, forCellWithReuseIdentifier: MPPositive_ArtistShowCollectionViewCellID)
return collectionView
}()
private let MPPositive_ArtistShowListCollectionViewCellID = "MPPositive_ArtistShowListCollectionViewCell"
private let MPPositive_ArtistShowCollectionViewCellID = "MPPositive_ArtistShowCollectionViewCell"
var itemViews:[MPPositive_BrowseItemViewModel]!
///
var showType:ArtistType = .List{
didSet{
collectionView.collectionViewLayout = showType.layout
collectionView.reloadData()
}
}
//
var chooseItemBlock:((MPPositive_BrowseItemViewModel) -> Void)?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
backgroundColor = .clear
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
private func configure() {
contentView.addSubview(collectionView)
collectionView.snp.makeConstraints { make in
make.left.right.top.equalToSuperview()
make.bottom.equalToSuperview().offset(-1*width).priority(999)
}
}
//tableViewCell
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
let size = super.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: horizontalFittingPriority, verticalFittingPriority: verticalFittingPriority)
collectionView.layoutIfNeeded()
switch showType {
case .List:
//
let height = collectionView.collectionViewLayout.collectionViewContentSize.height
return CGSize(width: size.width, height: size.height + height)
case .Artist:
//
let height = showType.layout.itemSize.height + showType.layout.sectionInset.top + showType.layout.sectionInset.bottom
return CGSize(width: size.width, height: size.height + height)
}
}
}
//MARK: - tableView
extension MPPositive_ArtistShowListableViewCell:UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return itemViews?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
switch showType {
case .List:
//
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MPPositive_ArtistShowListCollectionViewCellID, for: indexPath) as! MPPositive_ArtistShowListCollectionViewCell
cell.itemView = itemViews[indexPath.row]
return cell
case .Artist:
//
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MPPositive_ArtistShowCollectionViewCellID, for: indexPath) as! MPPositive_ArtistShowCollectionViewCell
cell.itemView = itemViews[indexPath.row]
return cell
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if chooseItemBlock != nil {
chooseItemBlock!(itemViews[indexPath.row])
}
}
}

View File

@ -0,0 +1,105 @@
//
// MPPositive_ArtistShowSongTableViewCell.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/16.
//
import UIKit
class MPPositive_ArtistShowSongTableViewCell: UITableViewCell {
//
private lazy var iconImageView:UIImageView = {
let imageView:UIImageView = .init()
imageView.contentMode = .scaleAspectFill
imageView.layer.masksToBounds = true
return imageView
}()
private lazy var titleLabel:UILabel = createLabel(font: .systemFont(ofSize: 14*width, weight: .regular), textColor: .white, textAlignment: .left)
private lazy var subtitleLabel:UILabel = createLabel(font: .systemFont(ofSize: 12*width, weight: .regular), textColor: .init(hex: "#FFFFFF", alpha: 0.6), textAlignment: .left)
///
private lazy var moreBtn:UIButton = {
let btn:UIButton = .init()
btn.setBackgroundImage(UIImage(named: "Song_More'logo"), for: .normal)
btn.addTarget(self, action: #selector(moreActionClick(_ :)), for: .touchUpInside)
return btn
}()
///
private lazy var loadBtn:UIButton = {
let btn:UIButton = .init()
btn.setBackgroundImage(UIImage(named: "Song_Unload'logo"), for: .normal)
btn.setBackgroundImage(UIImage(named: "Song_Loaded'logo"), for: .selected)
btn.addTarget(self, action: #selector(loadActionClick(_ :)), for: .touchUpInside)
return btn
}()
var itemView:MPPositive_BrowseItemViewModel!{
didSet{
itemView.setUrltoImage(iconImageView)
titleLabel.text = itemView.title
subtitleLabel.text = itemView.subtitle
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
backgroundColor = .clear
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
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
}
private func configure() {
contentView.addSubview(iconImageView)
iconImageView.snp.makeConstraints { make in
make.width.height.equalTo(60*width)
make.top.equalToSuperview().offset(8*width).priority(999)
make.bottom.equalToSuperview().offset(-8*width)
make.left.equalToSuperview().offset(18*width)
}
contentView.addSubview(moreBtn)
moreBtn.snp.makeConstraints { make in
make.width.height.equalTo(24*width)
make.centerY.equalTo(iconImageView.snp.centerY)
make.right.equalToSuperview().offset(-18*width)
}
contentView.addSubview(loadBtn)
loadBtn.snp.makeConstraints { make in
make.width.height.equalTo(24*width)
make.centerY.equalTo(iconImageView.snp.centerY)
make.right.equalToSuperview().offset(-54*width)
}
contentView.addSubview(titleLabel)
titleLabel.snp.makeConstraints { make in
make.top.equalTo(iconImageView.snp.top).offset(10*width)
make.left.equalTo(iconImageView.snp.right).offset(12*width)
make.right.equalTo(loadBtn.snp.left).offset(-10*width)
}
contentView.addSubview(subtitleLabel)
subtitleLabel.snp.makeConstraints { make in
make.bottom.equalTo(iconImageView.snp.bottom).offset(-10*width)
make.left.equalTo(titleLabel.snp.left)
make.right.equalTo(titleLabel.snp.right)
}
}
//
@objc private func moreActionClick(_ sender:UIButton) {
}
//
@objc private func loadActionClick(_ sender:UIButton) {
}
}

View File

@ -0,0 +1,197 @@
//
// MPPositive_ArtistShowTypeView.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/15.
//
import UIKit
import MJRefresh
class MPPositive_ArtistShowTypeView: UIView, JXPagingViewListViewDelegate {
//tableView
private lazy var tableView:UITableView = {
let tableView = UITableView()
if #available(iOS 15.0, *) {
tableView.sectionHeaderTopPadding = 0
}
tableView.backgroundColor = .clear
tableView.separatorStyle = .none
tableView.estimatedRowHeight = 200
tableView.rowHeight = UITableView.automaticDimension
tableView.dataSource = self
tableView.delegate = self
tableView.register(MPPositive_ArtistShowSongTableViewCell.self, forCellReuseIdentifier: MPPositive_ArtistShowSongTableViewCellID)
tableView.register(MPPositive_ArtistShowListableViewCell.self, forCellReuseIdentifier: MPPositive_ArtistShowListableViewCellID)
tableView.register(MPPositive_ArtistDescriptionTableViewCell.self, forCellReuseIdentifier: MPPositive_ArtistDescriptionTableViewCellID)
//
let footer = MJRefreshAutoGifFooter {
[weak self] in
self?.footerRefresh()
}
footer.setTitle("Pull Up", for: .idle)
footer.setTitle("Release Refresh", for: .pulling)
footer.setTitle("Loading...", for: .refreshing)
footer.setTitle("No Data", for: .noMoreData)
tableView.mj_footer = footer
return tableView
}()
private let MPPositive_ArtistShowSongTableViewCellID = "MPPositive_ArtistShowSongTableViewCell"
private let MPPositive_ArtistShowListableViewCellID = "MPPositive_ArtistShowListableViewCell"
private let MPPositive_ArtistDescriptionTableViewCellID = "MPPositive_ArtistDescriptionTableViewCell"
private var sectionList:MPPositive_ArtistContentListViewModel!{
didSet{
if sectionList != nil {
//
if sectionList.browseId != nil && sectionList.params != nil {
//
sectionList.requestArtistType()
}else {
//
tableView.reloadData()
tableView.mj_footer?.endRefreshing()
}
sectionList.completionBlock = {
[weak self] in
self?.tableView.reloadData()
self?.tableView.mj_footer?.endRefreshing()
}
}else {
tableView.reloadData()
}
}
}
var header:MPPositive_ArtistHeaderModel!
//scroll
fileprivate var listViewDidScrollCallback: ((UIScrollView) -> ())?
//
var chooseItemBlock:((MPPositive_BrowseItemViewModel) -> Void)?
init(frame: CGRect, list:MPPositive_ArtistContentListViewModel) {
super.init(frame: frame)
backgroundColor = .init(hex: "#151718")
//
DispatchQueue.main.async {
[weak self] in
self?.sectionList = list
}
addSubview(tableView)
tableView.snp.makeConstraints { make in
make.left.top.right.bottom.equalToSuperview()
}
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
//
@objc private func footerRefresh() {
guard sectionList.continuation != nil, sectionList.itct != nil else {
tableView.mj_footer?.endRefreshingWithNoMoreData()
return
}
sectionList.requestArtistContinuation()
}
//MARK: -
func scrollViewDidScroll(_ scrollView: UIScrollView) {
listViewDidScrollCallback?(scrollView)
}
//
func listView() -> UIView {
return self
}
//ScrollView
func listScrollView() -> UIScrollView {
return tableView
}
//
func listViewDidScrollCallback(callback: @escaping (UIScrollView) -> ()) {
listViewDidScrollCallback = callback
}
}
//MARK: - tableView
extension MPPositive_ArtistShowTypeView:UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let first = sectionList?.itemViews.first {
switch first.browseItem.itemType {
case .single:
return sectionList.itemViews.count
case .list:
return 1
case .artist:
return 2
default:
return 0
}
}else {
return 0
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let first = sectionList?.itemViews.first {
switch first.browseItem.itemType {
case .single:
let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_ArtistShowSongTableViewCellID) as! MPPositive_ArtistShowSongTableViewCell
cell.itemView = sectionList.itemViews[indexPath.row]
return cell
case .list:
let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_ArtistShowListableViewCellID, for: indexPath) as! MPPositive_ArtistShowListableViewCell
cell.itemViews = sectionList.itemViews
cell.chooseItemBlock = {
[weak self] item in
guard let self = self else {return}
if chooseItemBlock != nil {
chooseItemBlock!(item)
}
}
cell.showType = .List
return cell
case .artist:
switch indexPath.row {
case 0:
let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_ArtistShowListableViewCellID, for: indexPath) as! MPPositive_ArtistShowListableViewCell
cell.itemViews = sectionList.itemViews
cell.chooseItemBlock = {
[weak self] item in
guard let self = self else {return}
if chooseItemBlock != nil {
chooseItemBlock!(item)
}
}
cell.showType = .Artist
return cell
default:
let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_ArtistDescriptionTableViewCellID, for: indexPath) as! MPPositive_ArtistDescriptionTableViewCell
cell.titleLabel.text = header?.fordescription ?? ""
return cell
}
default:
let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_ArtistShowSongTableViewCellID) as! MPPositive_ArtistShowSongTableViewCell
return cell
}
}else {
let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_ArtistShowSongTableViewCellID) as! MPPositive_ArtistShowSongTableViewCell
return cell
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let first = sectionList?.itemViews.first {
switch first.browseItem.itemType {
case .single:
//cell
if chooseItemBlock != nil {
chooseItemBlock!(sectionList.itemViews[indexPath.row])
}
case .list:
//
break
case .artist:
//
break
default:
break
}
}
}
}

View File

@ -8,6 +8,9 @@
import UIKit
//BView(View)
class MPPositive_PlayerCoverView: UIView {
//View
private var loadView = CircularProgressView()
///
lazy var coverImageView:UIImageView = {
let imageView = UIImageView()
@ -44,6 +47,14 @@ class MPPositive_PlayerCoverView: UIView {
sliderView.addTarget(self, action: #selector(seekProgressClick(_:forEvent:)), for: .touchUpInside)
return sliderView
}()
///View
lazy var progressView:UIProgressView = {
let progressView:UIProgressView = .init()
progressView.isUserInteractionEnabled = true
progressView.progressTintColor = .init(hex: "#FFFFFF", alpha: 0.3)
progressView.trackTintColor = .clear
return progressView
}()
///Label
lazy var durationLabel:UILabel = createLabel("00:00" ,font: .systemFont(ofSize: 12*width, weight: .medium), textColor: .init(hex: "#FFFFFF", alpha: 0.85), textAlignment: .left)
///Label
@ -93,14 +104,18 @@ class MPPositive_PlayerCoverView: UIView {
make.centerY.equalTo(loadBtn.snp.centerY)
make.width.height.equalTo(24*width)
}
//label
addSubview(sliderView)
sliderView.snp.makeConstraints { make in
addSubview(progressView)
progressView.snp.makeConstraints { make in
make.top.equalTo(subtitleLabel.snp.bottom).offset(25*width)
make.centerX.equalToSuperview()
make.width.equalTo(335*width)
make.height.equalTo(6*width)
}
//label
addSubview(sliderView)
sliderView.snp.makeConstraints { make in
make.left.right.top.bottom.equalTo(progressView)
}
addSubview(durationLabel)
durationLabel.snp.makeConstraints { make in
make.left.equalTo(sliderView.snp.left)
@ -137,13 +152,57 @@ class MPPositive_PlayerCoverView: UIView {
}
//
@objc private func loadActionClick(_ sender:UIButton) {
//
addCircularProgressBar(over: sender)
self.loadBtn.setBackgroundImage(UIImage(named: ""), for: .normal)
self.loadBtn.setImage(UIImage(named: "download"), for: .normal)
//
if MP_PlayerManager.shared.loadPlayer.currentVideo != nil {
//
//
//
if let currentVideo = MP_PlayerManager.shared.loadPlayer.currentVideo, let videoURLString = currentVideo.song.resourceUrls?.first, let videoURL = URL(string: videoURLString) {
let videoId = currentVideo.song.videoId ?? "default_video_id"
DownloadManager.shared.downloadVideo(from: videoURL, videoId: videoId, progressView: loadView, completion: { [weak self] result in
switch result {
case .success(let fileURL):
let item = MPPositive_DownloadItemModel.create()
item.resourcePath = "\(fileURL)"
item.coverImage = URL(string: MP_PlayerManager.shared.loadPlayer.currentVideo.song.coverUrls!.first!)
item.reviewImage = URL(string: MP_PlayerManager.shared.loadPlayer.currentVideo.song.reviewUrls!.first!)
item.title = MP_PlayerManager.shared.loadPlayer.currentVideo.song.title
item.longBylineText = MP_PlayerManager.shared.loadPlayer.currentVideo.song.longBylineText
item.lengthText = MP_PlayerManager.shared.loadPlayer.currentVideo.song.lengthText
item.shortBylineText = MP_PlayerManager.shared.loadPlayer.currentVideo.song.shortBylineText
item.lyrics = MP_PlayerManager.shared.loadPlayer.currentVideo.lyrics
item.videoId = MP_PlayerManager.shared.loadPlayer.currentVideo.song.videoId
item.relatedID = MP_PlayerManager.shared.loadPlayer.currentVideo.song.relatedID
MPPositive_DownloadItemModel.save()
DispatchQueue.main.async {
self?.loadBtn.setBackgroundImage(UIImage(named: "Song_Loaded'logo"), for: .normal)
self?.loadBtn.setImage(UIImage(named: ""), for: .normal)
}
self?.loadView.removeFromSuperview()
case .failure(let error):
print("Download failed with error: \(error)")
self?.loadView.removeFromSuperview()
}
})
}
}
}
//
private func addCircularProgressBar(over button: UIButton) {
loadView.removeFromSuperview() //
loadView = CircularProgressView(frame: button.bounds)
addSubview(loadView)
loadView.snp.makeConstraints { make in
make.center.equalTo(button)
make.width.height.equalTo(button)
}
}
}

View File

@ -30,7 +30,7 @@ class MPPositive_PlayerListShowTableViewCell: UITableViewCell {
var song:MPPositive_SongItemModel!{
didSet{
//
if song.videoId == MP_PlayerManager.shared.loadPlayer.currentVideo.song.videoId {
if song.videoId == MP_PlayerManager.shared.loadPlayer.currentVideo?.song?.videoId {
titleLabel.textColor = .init(hex: "#80F988")
subtitleLabel.textColor = .init(hex: "#80F988")
}else {

View File

@ -8,12 +8,118 @@
import UIKit
///b
class MPPositive_PlayerSilder: UISlider {
//
var thumbImage:UIImage = .init(named: "Player_Slider'logo")!
//
var trackHeight: CGFloat = 6*width
//
var minTrackColors: [UIColor]!
//
var maxTrackColors:[UIColor]!
//()
var minTrackLocations:[CGFloat]!
//()
var maxTrackLocations:[CGFloat]!
//
override init(frame:CGRect) {
super.init(frame: frame)
setUpLayout()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
private func setUpLayout() {
layer.masksToBounds = false
setThumbImage(thumbImage, for: .normal)
//
let minTrackImg = makeTrackImage(rect: frame, colors: [UIColor.white], locations: [0,1])
setMinimumTrackImage(minTrackImg, for: .normal)
//
let maxTrackImg = makeTrackImage(rect: frame, colors: [UIColor(hex: "#FFFFFF", alpha: 0.1)], locations: [0,1])
setMaximumTrackImage(maxTrackImg, for: .normal)
}
///
/// - Parameters:
/// - rect:
/// - color:
/// - Returns:
private func makeThumbImage(rect: CGRect, color:UIColor) -> UIImage {
let lineWidth: CGFloat = 2
//
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0)
//
color.setFill()
//线
let oval = UIBezierPath(ovalIn: rect)
//
oval.fill()
//
UIColor.white.setStroke()
//
oval.lineWidth = lineWidth
oval.stroke()
//
let thumbImg = UIGraphicsGetImageFromCurrentImageContext()!
//
UIGraphicsEndImageContext()
return thumbImg
}
///
/// - Parameters:
/// - rect:
/// - colors:
/// - locations:
/// - Returns:
private func makeTrackImage(rect: CGRect, colors:[UIColor], locations:[CGFloat]) -> UIImage {
let rect = CGRect(x: rect.minX, y: rect.minY, width: rect.width, height: self.trackHeight)
//
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0)
let ctx = UIGraphicsGetCurrentContext()!
//
let cornerRadius: CGFloat = rect.height * 0.5
let path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).cgPath
//
ctx.addPath(path)
ctx.clip()
// 使rgb
let colorSpace = CGColorSpaceCreateDeviceRGB()
//cgcolors
var cgColors:[CGColor] = []
colors.forEach { item in
cgColors.append(item.cgColor)
}
//
let gradient:CGGradient = CGGradient(colorsSpace: colorSpace, colors: cgColors as CFArray, locations: locations)!
//
let start = CGPoint(x: self.bounds.minX, y: self.bounds.minY)
//
let end = CGPoint(x: self.bounds.maxX, y: self.bounds.minY)
//
ctx.drawLinearGradient(gradient, start: start, end: end, options: .drawsBeforeStartLocation)
//
let trackImg = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return trackImg
}
// thumb
override func thumbRect(forBounds bounds: CGRect, trackRect rect: CGRect, value: Float) -> CGRect {
let rect = super.thumbRect(forBounds: bounds, trackRect: rect, value: value)
// thumb
// (setValue(_:animated:)animatedtruethumb)
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize(width: 0, height: 0)
self.layer.shadowOpacity = 0.15
self.layer.shadowRadius = 3
self.layer.shadowPath = UIBezierPath(cgPath: CGPath(ellipseIn: rect.insetBy(dx: 3, dy: 3), transform: nil)).cgPath
return rect
}
// track
override func trackRect(forBounds bounds: CGRect) -> CGRect {
return CGRect(x: 0, y: 0, width: bounds.width, height: self.trackHeight)
}
}

View File

@ -37,6 +37,8 @@ class MPPositive_SearchResultPreviewShowView: UIView, JXSegmentedListContainerVi
}
var scrollBlock:(() -> Void)?
var chooseMoreIndexBlock:((Int) -> Void)?
//
var chooseItemBlock:((MPPositive_SearchResultItemViewModel) -> Void)?
init(frame: CGRect, sectionLists:[MPPositive_SearchResultListViewModel]) {
super.init(frame: frame)
backgroundColor = .init(hex: "1A1A1A")
@ -111,4 +113,9 @@ extension MPPositive_SearchResultPreviewShowView:UITableViewDataSource, UITableV
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50*width
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if chooseItemBlock != nil {
chooseItemBlock!(sectionLists[indexPath.section].previewItemViews[indexPath.row])
}
}
}

View File

@ -6,6 +6,7 @@
//
import UIKit
import MJRefresh
///
class MPPositive_SearchResultTypeShowView: UIView, JXSegmentedListContainerViewListDelegate {
//tableView
@ -21,41 +22,56 @@ class MPPositive_SearchResultTypeShowView: UIView, JXSegmentedListContainerViewL
tableView.dataSource = self
tableView.delegate = self
tableView.register(MPPositive_SearchResultShowTableViewCell.self, forCellReuseIdentifier: MPPositive_SearchResultShowTableViewCellID)
//
let footer = MJRefreshAutoGifFooter {
[weak self] in
self?.footerRefresh()
}
footer.setTitle("Pull Up", for: .idle)
footer.setTitle("Release Refresh", for: .pulling)
footer.setTitle("Loading...", for: .refreshing)
footer.setTitle("No Data", for: .noMoreData)
tableView.mj_footer = footer
return tableView
}()
private let MPPositive_SearchResultShowTableViewCellID = "MPPositive_SearchResultShowTableViewCell"
//
private var sectionList:MPPositive_SearchResultListViewModel!{
var sectionList:MPPositive_SearchResultListViewModel!{
didSet{
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
if sectionList != nil {
if sectionList.allItemViews.count == 0 {
//
sectionList.requestSearchType()
}
sectionList.completionBlock = {
self.tableView.reloadData()
[weak self] in
self?.tableView.reloadData()
self?.tableView.mj_footer?.endRefreshing()
}
}else {
tableView.reloadData()
}
}
}
}
var scrollBlock:(() -> Void)?
init(frame: CGRect, sectionList:MPPositive_SearchResultListViewModel) {
init(frame: CGRect, list:MPPositive_SearchResultListViewModel) {
super.init(frame: frame)
backgroundColor = .init(hex: "1A1A1A")
self.sectionList = sectionList
DispatchQueue.main.async {
[weak self] in
self?.sectionList = list
}
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func awakeFromNib() {
super.awakeFromNib()
}
//MARK: -
func listView() -> UIView {
return self
@ -67,6 +83,14 @@ class MPPositive_SearchResultTypeShowView: UIView, JXSegmentedListContainerViewL
make.left.top.right.bottom.equalToSuperview()
}
}
//
@objc private func footerRefresh() {
guard sectionList.continuation != nil, sectionList.itct != nil else {
tableView.mj_footer?.endRefreshingWithNoMoreData()
return
}
sectionList.requestSearchContinuation()
}
}
//MARK: - tableView
extension MPPositive_SearchResultTypeShowView:UITableViewDataSource, UITableViewDelegate {

View File

@ -9,7 +9,7 @@ import UIKit
class MPPositive_SearchResultsShowView: UIView {
///
var loadModel:MPPositive_LoadSearchResultsViewModel!{
var loadModel:MPPositive_SearchResultsLoadViewModel!{
didSet{
DispatchQueue.main.async {
[weak self] in
@ -25,6 +25,7 @@ class MPPositive_SearchResultsShowView: UIView {
dataSource.titles = loadModel?.sectionLists.compactMap({$0.title}) ?? []
dataSource.reloadData(selectedIndex: 0)
segmentView.reloadData()
emptyImageView.isHidden = !(dataSource.titles.count == 0)
}
}
}
@ -68,10 +69,18 @@ class MPPositive_SearchResultsShowView: UIView {
listContainerView.backgroundColor = .clear
return listContainerView
}()
//
private lazy var emptyImageView:UIImageView = {
let imageView = UIImageView(image: UIImage(named: "empty"))
imageView.contentMode = .scaleAspectFill
return imageView
}()
var scrollBlock:(() -> Void)?
//
var chooseItemBlock:((MPPositive_SearchResultItemViewModel) -> Void)?
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .clear
backgroundColor = .init(hex: "1A1A1A")
configure()
}
@ -80,6 +89,12 @@ class MPPositive_SearchResultsShowView: UIView {
}
//
private func configure() {
addSubview(emptyImageView)
emptyImageView.snp.makeConstraints { make in
make.center.equalToSuperview()
make.width.equalTo(211*width)
make.height.equalTo(172*width)
}
segmentView.indicators = [indicator]
segmentView.dataSource = dataSource
//View
@ -120,10 +135,19 @@ extension MPPositive_SearchResultsShowView: JXSegmentedListContainerViewDataSour
}
segmentView.selectItemAt(index: selectedIndex)
}
showView.chooseItemBlock = {
[weak self] item in
guard let self = self else {
return
}
if chooseItemBlock != nil {
chooseItemBlock!(item)
}
}
return showView
}else {
//
let showView:MPPositive_SearchResultTypeShowView = .init(frame: listContainerView.frame, sectionList: loadModel.sectionLists[index])
let showView:MPPositive_SearchResultTypeShowView = .init(frame: listContainerView.frame, list: loadModel.sectionLists[index])
showView.scrollBlock = {
[weak self] in
if self?.scrollBlock != nil {

View File

@ -21,4 +21,7 @@ pod "Kingfisher"
#分页工具
pod 'JXSegmentedView'
pod 'JXPagingView/Paging'
#刷新支持
pod 'MJRefresh'
end

View File

@ -1,11 +1,14 @@
PODS:
- Alamofire (5.9.1)
- IQKeyboardManagerSwift (6.5.13)
- IQKeyboardManagerSwift (6.5.16)
- JXPagingView/Paging (2.1.3)
- JXSegmentedView (1.3.3)
- Kingfisher (7.11.0)
- SnapKit (5.6.0)
- SVProgressHUD (2.2.5)
- MJRefresh (3.7.9)
- SnapKit (5.7.1)
- SVProgressHUD (2.3.1):
- SVProgressHUD/Core (= 2.3.1)
- SVProgressHUD/Core (2.3.1)
- SwiftDate (6.3.1)
DEPENDENCIES:
@ -14,6 +17,7 @@ DEPENDENCIES:
- JXPagingView/Paging
- JXSegmentedView
- Kingfisher
- MJRefresh
- SnapKit
- SVProgressHUD
- SwiftDate
@ -25,20 +29,22 @@ SPEC REPOS:
- JXPagingView
- JXSegmentedView
- Kingfisher
- MJRefresh
- SnapKit
- SVProgressHUD
- SwiftDate
SPEC CHECKSUMS:
Alamofire: f36a35757af4587d8e4f4bfa223ad10be2422b8c
IQKeyboardManagerSwift: 69b5fb9960edff37263d45c44fd97427e1f8c22b
IQKeyboardManagerSwift: 12d89768845bb77b55cc092ecc2b1f9370f06b76
JXPagingView: afdd2e9af09c90160dd232b970d603cc6e7ddd0e
JXSegmentedView: 651b60fcf705258ba9395edd53876dbd2853fb68
Kingfisher: b9c985d864d43515f404f1ef4a8ce7d802ace3ac
SnapKit: e01d52ebb8ddbc333eefe2132acf85c8227d9c25
SVProgressHUD: 1428aafac632c1f86f62aa4243ec12008d7a51d6
MJRefresh: ff9e531227924c84ce459338414550a05d2aea78
SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a
SVProgressHUD: 4837c74bdfe2e51e8821c397825996a8d7de6e22
SwiftDate: 72d28954e8e1c6c1c0f917ccc8005e4f83c7d4b2
PODFILE CHECKSUM: c0bbca53cdc7c53ea7a9dbc8d75b7ae964a1dbc2
PODFILE CHECKSUM: ba88795291c32ea83d380e5384537ca7f5568cd7
COCOAPODS: 1.15.2

View File

@ -51,9 +51,9 @@ public extension IQKeyboardManager {
// Registering for keyboard notification.
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidShow(_:)), name: UIResponder.keyboardDidShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidHide(_:)), name: UIResponder.keyboardDidHideNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive(_:)), name: UIApplication.didBecomeActiveNotification, object: nil)
// Registering for UITextField notification.
registerTextFieldViewClass(UITextField.self, didBeginEditingNotificationName: UITextField.textDidBeginEditingNotification.rawValue, didEndEditingNotificationName: UITextField.textDidEndEditingNotification.rawValue)
@ -69,9 +69,9 @@ public extension IQKeyboardManager {
// Unregistering for keyboard notification.
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardDidShowNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardDidHideNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil)
// Unregistering for UITextField notification.
unregisterTextFieldViewClass(UITextField.self, didBeginEditingNotificationName: UITextField.textDidBeginEditingNotification.rawValue, didEndEditingNotificationName: UITextField.textDidEndEditingNotification.rawValue)

View File

@ -37,7 +37,6 @@ public extension IQKeyboardManager {
static var startingTextViewContentInsets: Int = 0
static var startingTextViewScrollIndicatorInsets: Int = 0
static var isTextViewContentInsetChanged: Int = 0
static var hasPendingAdjustRequest: Int = 0
}
/**
@ -136,32 +135,23 @@ public extension IQKeyboardManager {
}
}
/** To know if we have any pending request to adjust view position. */
private var hasPendingAdjustRequest: Bool {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.hasPendingAdjustRequest) as? Bool ?? false
}
set(newValue) {
objc_setAssociatedObject(self, &AssociatedKeys.hasPendingAdjustRequest, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
@objc internal func applicationDidBecomeActive(_ notificatin: Notification) {
internal func optimizedAdjustPosition() {
if !hasPendingAdjustRequest {
hasPendingAdjustRequest = true
OperationQueue.main.addOperation {
guard privateIsEnabled(),
keyboardShowing,
topViewBeginOrigin.equalTo(IQKeyboardManager.kIQCGPointInvalid) == false, let textFieldView = textFieldView,
textFieldView.isAlertViewTextField() == false else {
return
}
self.adjustPosition()
self.hasPendingAdjustRequest = false
}
}
}
// swiftlint:disable function_body_length
/* Adjusting RootViewController's frame according to interface orientation. */
private func adjustPosition() {
internal func adjustPosition() {
// We are unable to get textField object while keyboard showing on WKWebView's textField. (Bug ID: #11)
guard hasPendingAdjustRequest,
guard UIApplication.shared.applicationState == .active,
let textFieldView = textFieldView,
let rootController = textFieldView.parentContainerViewController(),
let window = keyWindow(),
@ -177,16 +167,20 @@ public extension IQKeyboardManager {
var rootViewOrigin = rootController.view.frame.origin
// Maintain keyboardDistanceFromTextField
var specialKeyboardDistanceFromTextField = textFieldView.keyboardDistanceFromTextField
let specialKeyboardDistanceFromTextField: CGFloat
if let searchBar = textFieldView.textFieldSearchBar() {
specialKeyboardDistanceFromTextField = searchBar.keyboardDistanceFromTextField
} else {
specialKeyboardDistanceFromTextField = textFieldView.keyboardDistanceFromTextField
}
let newKeyboardDistanceFromTextField = (specialKeyboardDistanceFromTextField == kIQUseDefaultKeyboardDistance) ? keyboardDistanceFromTextField : specialKeyboardDistanceFromTextField
var kbSize = keyboardFrame.size
let kbSize: CGSize
let originalKbSize: CGSize
// Calculating actual keyboard covered size respect to window, keyboard frame may be different when hardware keyboard is attached (Bug ID: #469) (Bug ID: #381) (Bug ID: #1506)
do {
var kbFrame = keyboardFrame
@ -196,9 +190,7 @@ public extension IQKeyboardManager {
kbFrame.origin.y -= topViewBeginSafeAreaInsets.bottom
kbFrame.size.height += topViewBeginSafeAreaInsets.bottom
// Calculating actual keyboard covered size respect to window, keyboard frame may be different when hardware keyboard is attached (Bug ID: #469) (Bug ID: #381) (Bug ID: #1506)
let intersectRect = kbFrame.intersection(window.frame)
if intersectRect.isNull {
kbSize = CGSize(width: kbFrame.size.width, height: 0)
} else {
@ -206,6 +198,15 @@ public extension IQKeyboardManager {
}
}
do {
let intersectRect = keyboardFrame.intersection(window.frame)
if intersectRect.isNull {
originalKbSize = CGSize(width: keyboardFrame.size.width, height: 0)
} else {
originalKbSize = intersectRect.size
}
}
let statusBarHeight: CGFloat
let navigationBarAreaHeight: CGFloat
@ -224,39 +225,35 @@ public extension IQKeyboardManager {
navigationBarAreaHeight = statusBarHeight
}
let layoutAreaHeight: CGFloat = rootController.view.layoutMargins.bottom
let layoutAreaHeight: CGFloat = rootController.view.directionalLayoutMargins.top
let isTextView: Bool
let isNonScrollableTextView: Bool
let isScrollableTextView: Bool
if let textView = textFieldView as? UIScrollView, textFieldView.responds(to: #selector(getter: UITextView.isEditable)) {
isTextView = true
isNonScrollableTextView = !textView.isScrollEnabled
isScrollableTextView = textView.isScrollEnabled
} else {
isTextView = false
isNonScrollableTextView = false
isScrollableTextView = false
}
let topLayoutGuide: CGFloat = max(navigationBarAreaHeight, layoutAreaHeight) + 5
let topLayoutGuide: CGFloat = max(navigationBarAreaHeight, layoutAreaHeight)
// Validation of textView for case where there is a tab bar at the bottom or running on iPhone X and textView is at the bottom.
let bottomLayoutGuide: CGFloat = (isTextView && !isNonScrollableTextView) ? 0 : rootController.view.layoutMargins.bottom
let visibleHeight: CGFloat = window.frame.height-kbSize.height
let bottomLayoutGuide: CGFloat = isScrollableTextView ? 0 : rootController.view.directionalLayoutMargins.bottom
// Move positive = textField is hidden.
// Move negative = textField is showing.
// Calculating move position.
var move: CGFloat
var moveUp: CGFloat
// Special case: when the textView is not scrollable, then we'll be scrolling to the bottom part and let hide the top part above
if isNonScrollableTextView {
move = textFieldViewRectInWindow.maxY - visibleHeight + bottomLayoutGuide
} else {
move = min(textFieldViewRectInRootSuperview.minY-(topLayoutGuide), textFieldViewRectInWindow.maxY - visibleHeight + bottomLayoutGuide)
do {
let visibleHeight: CGFloat = window.frame.height-kbSize.height
let topMovement: CGFloat = textFieldViewRectInRootSuperview.minY-topLayoutGuide
let bottomMovement: CGFloat = textFieldViewRectInWindow.maxY - visibleHeight + bottomLayoutGuide
moveUp = min(topMovement, bottomMovement)
}
showLog("Need to move: \(move)")
showLog("Need to move: \(moveUp), will be moving \(moveUp < 0 ? "down" : "up")")
var superScrollView: UIScrollView?
var superView = textFieldView.superviewOfClassType(UIScrollView.self) as? UIScrollView
@ -299,9 +296,9 @@ public extension IQKeyboardManager {
}
}
startingContentInsets = UIEdgeInsets()
startingScrollIndicatorInsets = UIEdgeInsets()
startingContentOffset = CGPoint.zero
startingContentInsets = .zero
startingScrollIndicatorInsets = .zero
startingContentOffset = .zero
self.lastScrollView = nil
} else if superScrollView != lastScrollView { // If both scrollView's are different, then reset lastScrollView to it's original frame and setting current scrollView as last scrollView.
@ -374,11 +371,11 @@ public extension IQKeyboardManager {
var shouldContinue = false
if move > 0 {
shouldContinue = move > (-scrollView.contentOffset.y - scrollView.contentInset.top)
if moveUp > 0 {
shouldContinue = moveUp > (-scrollView.contentOffset.y - scrollView.contentInset.top)
} else if let tableView = scrollView.superviewOfClassType(UITableView.self) as? UITableView {
// Special treatment for UITableView due to their cell reusing logic
shouldContinue = scrollView.contentOffset.y > 0
if shouldContinue, let tableCell = textFieldView.superviewOfClassType(UITableViewCell.self) as? UITableViewCell, let indexPath = tableView.indexPath(for: tableCell), let previousIndexPath = tableView.previousIndexPath(of: indexPath) {
@ -387,11 +384,11 @@ public extension IQKeyboardManager {
if !previousCellRect.isEmpty {
let previousCellRectInRootSuperview = tableView.convert(previousCellRect, to: rootController.view.superview)
move = min(0, previousCellRectInRootSuperview.maxY - topLayoutGuide)
moveUp = min(0, previousCellRectInRootSuperview.maxY - topLayoutGuide)
}
}
} else if let collectionView = scrollView.superviewOfClassType(UICollectionView.self) as? UICollectionView {
// Special treatment for UITableView due to their cell reusing logic
shouldContinue = scrollView.contentOffset.y > 0
if shouldContinue, let collectionCell = textFieldView.superviewOfClassType(UICollectionViewCell.self) as? UICollectionViewCell, let indexPath = collectionView.indexPath(for: collectionCell), let previousIndexPath = collectionView.previousIndexPath(of: indexPath), let attributes = collectionView.layoutAttributesForItem(at: previousIndexPath) {
@ -400,23 +397,15 @@ public extension IQKeyboardManager {
if !previousCellRect.isEmpty {
let previousCellRectInRootSuperview = collectionView.convert(previousCellRect, to: rootController.view.superview)
move = min(0, previousCellRectInRootSuperview.maxY - topLayoutGuide)
moveUp = min(0, previousCellRectInRootSuperview.maxY - topLayoutGuide)
}
}
} else {
if isNonScrollableTextView {
shouldContinue = textFieldViewRectInWindow.maxY < visibleHeight + bottomLayoutGuide
if shouldContinue {
move = min(0, textFieldViewRectInWindow.maxY - visibleHeight + bottomLayoutGuide)
}
} else {
shouldContinue = textFieldViewRectInRootSuperview.minY < topLayoutGuide
if shouldContinue {
move = min(0, textFieldViewRectInRootSuperview.minY - topLayoutGuide)
}
moveUp = min(0, textFieldViewRectInRootSuperview.minY - topLayoutGuide)
}
}
@ -439,21 +428,16 @@ public extension IQKeyboardManager {
if let lastViewRect = lastView.superview?.convert(lastView.frame, to: scrollView) {
// Calculating the expected Y offset from move and scrollView's contentOffset.
var shouldOffsetY = scrollView.contentOffset.y - min(scrollView.contentOffset.y, -move)
var shouldOffsetY = scrollView.contentOffset.y - min(scrollView.contentOffset.y, -moveUp)
// Rearranging the expected Y offset according to the view.
if isNonScrollableTextView {
shouldOffsetY = min(shouldOffsetY, lastViewRect.maxY - visibleHeight + bottomLayoutGuide)
} else {
shouldOffsetY = min(shouldOffsetY, lastViewRect.minY)
}
// [_textFieldView isKindOfClass:[UITextView class]] If is a UITextView type
// nextScrollView == nil If processing scrollView is last scrollView in upper hierarchy (there is no other scrollView upper hierrchy.)
// [_textFieldView isKindOfClass:[UITextView class]] If is a UITextView type
// shouldOffsetY >= 0 shouldOffsetY must be greater than in order to keep distance from navigationBar (Bug ID: #92)
if isTextView, !isNonScrollableTextView,
if isScrollableTextView,
nextScrollView == nil,
shouldOffsetY >= 0 {
@ -467,14 +451,14 @@ public extension IQKeyboardManager {
shouldOffsetY = min(shouldOffsetY, scrollView.contentOffset.y + expectedFixDistance)
// Setting move to 0 because now we don't want to move any view anymore (All will be managed by our contentInset logic.
move = 0
moveUp = 0
} else {
// Subtracting the Y offset from the move variable, because we are going to change scrollView's contentOffset.y to shouldOffsetY.
move -= (shouldOffsetY-scrollView.contentOffset.y)
moveUp -= (shouldOffsetY-scrollView.contentOffset.y)
}
} else {
// Subtracting the Y offset from the move variable, because we are going to change scrollView's contentOffset.y to shouldOffsetY.
move -= (shouldOffsetY-scrollView.contentOffset.y)
moveUp -= (shouldOffsetY-scrollView.contentOffset.y)
}
let newContentOffset = CGPoint(x: scrollView.contentOffset.x, y: shouldOffsetY)
@ -482,7 +466,7 @@ public extension IQKeyboardManager {
if scrollView.contentOffset.equalTo(newContentOffset) == false {
showLog("old contentOffset: \(scrollView.contentOffset) new contentOffset: \(newContentOffset)")
self.showLog("Remaining Move: \(move)")
self.showLog("Remaining Move: \(moveUp)")
// Getting problem while using `setContentOffset:animated:`, So I used animation API.
UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in
@ -508,7 +492,7 @@ public extension IQKeyboardManager {
lastView = scrollView
superScrollView = nextScrollView
} else {
move = 0
moveUp = 0
break
}
}
@ -518,7 +502,7 @@ public extension IQKeyboardManager {
lastScrollView.shouldIgnoreContentInsetAdjustment == false {
var bottomInset: CGFloat = (kbSize.height)-(window.frame.height-lastScrollViewRect.maxY)
var bottomScrollIndicatorInset = bottomInset - newKeyboardDistanceFromTextField
var bottomScrollIndicatorInset = bottomInset - newKeyboardDistanceFromTextField - topViewBeginSafeAreaInsets.bottom
// Update the insets so that the scroll vew doesn't shift incorrectly when the offset is near the bottom of the scroll view.
bottomInset = max(startingContentInsets.bottom, bottomInset)
@ -559,9 +543,9 @@ public extension IQKeyboardManager {
// Special case for UITextView(Readjusting textView.contentInset when textView hight is too big to fit on screen)
// _lastScrollView If not having inside any scrollView, (now contentInset manages the full screen textView.
// [_textFieldView isKindOfClass:[UITextView class]] If is a UITextView type
if let textView = textFieldView as? UIScrollView, textView.isScrollEnabled, textFieldView.responds(to: #selector(getter: UITextView.isEditable)) {
if isScrollableTextView, let textView = textFieldView as? UIScrollView {
let keyboardYPosition = window.frame.height - (kbSize.height-newKeyboardDistanceFromTextField)
let keyboardYPosition = window.frame.height - originalKbSize.height
var rootSuperViewFrameInWindow = window.frame
if let rootSuperview = rootController.view.superview {
rootSuperViewFrameInWindow = rootSuperview.convert(rootSuperview.bounds, to: window)
@ -606,9 +590,9 @@ public extension IQKeyboardManager {
}
// +Positive or zero.
if move >= 0 {
if moveUp >= 0 {
rootViewOrigin.y = max(rootViewOrigin.y - move, min(0, -(kbSize.height-newKeyboardDistanceFromTextField)))
rootViewOrigin.y = max(rootViewOrigin.y - moveUp, min(0, -originalKbSize.height))
if rootController.view.frame.origin.equalTo(rootViewOrigin) == false {
showLog("Moving Upward")
@ -638,7 +622,7 @@ public extension IQKeyboardManager {
// disturbDistance positive = frame not disturbed.
if disturbDistance <= 0 {
rootViewOrigin.y -= max(move, disturbDistance)
rootViewOrigin.y -= max(moveUp, disturbDistance)
if rootController.view.frame.origin.equalTo(rootViewOrigin) == false {
showLog("Moving Downward")
@ -673,8 +657,6 @@ public extension IQKeyboardManager {
internal func restorePosition() {
hasPendingAdjustRequest = false
// Setting rootViewController frame to it's original position. // (Bug ID: #18)
guard topViewBeginOrigin.equalTo(IQKeyboardManager.kIQCGPointInvalid) == false, let rootViewController = rootViewController else {
return
@ -684,8 +666,6 @@ public extension IQKeyboardManager {
// Used UIViewAnimationOptionBeginFromCurrentState to minimize strange animations.
UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in
self.showLog("Restoring \(rootViewController) origin to: \(self.topViewBeginOrigin)")
// Setting it's new frame
var rect = rootViewController.view.frame
rect.origin = self.topViewBeginOrigin
@ -697,6 +677,8 @@ public extension IQKeyboardManager {
rootViewController.view.setNeedsLayout()
rootViewController.view.layoutIfNeeded()
}
self.showLog("Restoring \(rootViewController) origin to: \(self.topViewBeginOrigin)")
})
}

View File

@ -65,15 +65,34 @@ public extension IQKeyboardManager {
}
rightConfiguration.accessibilityLabel = toolbarDoneBarButtonItemAccessibilityLabel ?? "Done"
let isTableCollectionView: Bool
if textField.superviewOfClassType(UITableView.self) != nil ||
textField.superviewOfClassType(UICollectionView.self) != nil {
isTableCollectionView = true
} else {
isTableCollectionView = false
}
let shouldHavePreviousNext: Bool
switch previousNextDisplayMode {
case .default:
// If the textField is part of UITableView/UICollectionView then we should be exposing previous/next too
// Because at this time we don't know the previous or next cell if it contains another textField to move.
if isTableCollectionView {
shouldHavePreviousNext = true
} else if siblings.count <= 1 {
// If only one object is found, then adding only Done button.
if (siblings.count <= 1 && previousNextDisplayMode == .default) || previousNextDisplayMode == .alwaysHide {
textField.addKeyboardToolbarWithTarget(target: self, titleText: (shouldShowToolbarPlaceholder ? textField.drawingToolbarPlaceholder: nil), titleAccessibilityLabel: toolbarTitlBarButtonItemAccessibilityLabel, rightBarButtonConfiguration: rightConfiguration, previousBarButtonConfiguration: nil, nextBarButtonConfiguration: nil)
textField.inputAccessoryView?.tag = IQKeyboardManager.kIQDoneButtonToolbarTag // (Bug ID: #78)
} else if previousNextDisplayMode == .default || previousNextDisplayMode == .alwaysShow {
shouldHavePreviousNext = false
} else {
shouldHavePreviousNext = true
}
case .alwaysShow:
shouldHavePreviousNext = true
case .alwaysHide:
shouldHavePreviousNext = false
}
if shouldHavePreviousNext {
let prevConfiguration: IQBarButtonItemConfiguration
if let doneBarButtonItemImage = toolbarPreviousBarButtonItemImage {
@ -99,6 +118,22 @@ public extension IQKeyboardManager {
textField.addKeyboardToolbarWithTarget(target: self, titleText: (shouldShowToolbarPlaceholder ? textField.drawingToolbarPlaceholder: nil), titleAccessibilityLabel: toolbarTitlBarButtonItemAccessibilityLabel, rightBarButtonConfiguration: rightConfiguration, previousBarButtonConfiguration: prevConfiguration, nextBarButtonConfiguration: nextConfiguration)
textField.inputAccessoryView?.tag = IQKeyboardManager.kIQPreviousNextButtonToolbarTag // (Bug ID: #78)
if isTableCollectionView {
// In case of UITableView (Special), the next/previous buttons should always be enabled. (Bug ID: #56)
textField.keyboardToolbar.previousBarButton.isEnabled = true
textField.keyboardToolbar.nextBarButton.isEnabled = true
} else {
// If firstTextField, then previous should not be enabled.
textField.keyboardToolbar.previousBarButton.isEnabled = (siblings.first != textField)
// If lastTextField then next should not be enaled.
textField.keyboardToolbar.nextBarButton.isEnabled = (siblings.last != textField)
}
} else {
textField.addKeyboardToolbarWithTarget(target: self, titleText: (shouldShowToolbarPlaceholder ? textField.drawingToolbarPlaceholder: nil), titleAccessibilityLabel: toolbarTitlBarButtonItemAccessibilityLabel, rightBarButtonConfiguration: rightConfiguration, previousBarButtonConfiguration: nil, nextBarButtonConfiguration: nil)
textField.inputAccessoryView?.tag = IQKeyboardManager.kIQDoneButtonToolbarTag // (Bug ID: #78)
}
let toolbar = textField.keyboardToolbar
@ -143,11 +178,6 @@ public extension IQKeyboardManager {
toolbar.titleBarButton.title = nil
}
// In case of UITableView (Special), the next/previous buttons has to be refreshed everytime. (Bug ID: #56)
textField.keyboardToolbar.previousBarButton.isEnabled = (siblings.first != textField) // If firstTextField, then previous should not be enabled.
textField.keyboardToolbar.nextBarButton.isEnabled = (siblings.last != textField) // If lastTextField then next should not be enaled.
let elapsedTime = CACurrentMediaTime() - startTime
showLog("<<<<< \(#function) ended: \(elapsedTime) seconds <<<<<", indentation: -1)
}

View File

@ -161,7 +161,11 @@ public extension IQKeyboardManager {
}
// Getting keyboard animation duration
animationDuration = info[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval ?? 0.25
if let duration = info[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval, duration != 0 {
animationDuration = duration
} else {
animationDuration = 0.25
}
// Getting UIKeyboardSize.
if let kbFrame = info[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect {
@ -215,7 +219,7 @@ public extension IQKeyboardManager {
textFieldView.isAlertViewTextField() == false {
// keyboard is already showing. adjust position.
optimizedAdjustPosition()
self.adjustPosition()
}
}
@ -223,26 +227,6 @@ public extension IQKeyboardManager {
showLog("⌨️<<<<< \(#function) ended: \(elapsedTime) seconds <<<<<", indentation: -1)
}
/* UIKeyboardDidShowNotification. */
@objc internal func keyboardDidShow(_ notification: Notification) {
guard privateIsEnabled(),
let textFieldView = textFieldView,
let parentController = textFieldView.parentContainerViewController(), (parentController.modalPresentationStyle == UIModalPresentationStyle.formSheet || parentController.modalPresentationStyle == UIModalPresentationStyle.pageSheet),
textFieldView.isAlertViewTextField() == false else {
return
}
let startTime = CACurrentMediaTime()
showLog("⌨️>>>>> \(#function) started >>>>>", indentation: 1)
showLog("Notification Object:\(notification.object ?? "NULL")")
self.optimizedAdjustPosition()
let elapsedTime = CACurrentMediaTime() - startTime
showLog("⌨️<<<<< \(#function) ended: \(elapsedTime) seconds <<<<<", indentation: -1)
}
/* UIKeyboardWillHideNotification. So setting rootViewController to it's default frame. */
@objc internal func keyboardWillHide(_ notification: Notification?) {
@ -264,7 +248,11 @@ public extension IQKeyboardManager {
}
// Getting keyboard animation duration
animationDuration = info[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval ?? 0.25
if let duration = info[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval, duration != 0 {
animationDuration = duration
} else {
animationDuration = 0.25
}
}
// If not enabled then do nothing.
@ -339,29 +327,14 @@ public extension IQKeyboardManager {
// Reset all values
lastScrollView = nil
keyboardFrame = CGRect.zero
keyboardFrame = .zero
notifyKeyboardSize(size: keyboardFrame.size)
startingContentInsets = UIEdgeInsets()
startingScrollIndicatorInsets = UIEdgeInsets()
startingContentInsets = .zero
startingScrollIndicatorInsets = .zero
startingContentOffset = CGPoint.zero
// topViewBeginRect = CGRectZero //Commented due to #82
let elapsedTime = CACurrentMediaTime() - startTime
showLog("⌨️<<<<< \(#function) ended: \(elapsedTime) seconds <<<<<", indentation: -1)
}
@objc internal func keyboardDidHide(_ notification: Notification) {
let startTime = CACurrentMediaTime()
showLog("⌨️>>>>> \(#function) started >>>>>", indentation: 1)
showLog("Notification Object:\(notification.object ?? "NULL")")
topViewBeginOrigin = IQKeyboardManager.kIQCGPointInvalid
topViewBeginSafeAreaInsets = .zero
keyboardFrame = CGRect.zero
notifyKeyboardSize(size: keyboardFrame.size)
let elapsedTime = CACurrentMediaTime() - startTime
showLog("⌨️<<<<< \(#function) ended: \(elapsedTime) seconds <<<<<", indentation: -1)
}

View File

@ -168,7 +168,7 @@ internal extension IQKeyboardManager {
textFieldView.isAlertViewTextField() == false {
// keyboard is already showing. adjust position.
optimizedAdjustPosition()
self.adjustPosition()
}
}

View File

@ -318,12 +318,6 @@ Codeless drop-in universal library allows to prevent issues of keyboard sliding
// Creating gesture for @shouldResignOnTouchOutside. (Enhancement ID: #14)
resignFirstResponderGesture.isEnabled = shouldResignOnTouchOutside
// Loading IQToolbar, IQTitleBarButtonItem, IQBarButtonItem to fix first time keyboard appearance delay (Bug ID: #550)
// If you experience exception breakpoint issue at below line then try these solutions https://stackoverflow.com/questions/27375640/all-exception-break-point-is-stopping-for-no-reason-on-simulator
let textField = UITextField()
textField.addDoneOnKeyboardWithTarget(nil, action: #selector(self.doneAction(_:)))
textField.addPreviousNextDoneOnKeyboardWithTarget(nil, previousAction: #selector(self.previousAction(_:)), nextAction: #selector(self.nextAction(_:)), doneAction: #selector(self.doneAction(_:)))
disabledDistanceHandlingClasses.append(UITableViewController.self)
disabledDistanceHandlingClasses.append(UIAlertController.self)
disabledToolbarClasses.append(UIAlertController.self)
@ -333,6 +327,14 @@ Codeless drop-in universal library allows to prevent issues of keyboard sliding
toolbarPreviousNextAllowedClasses.append(IQPreviousNextView.self)
touchResignedGestureIgnoreClasses.append(UIControl.self)
touchResignedGestureIgnoreClasses.append(UINavigationBar.self)
// Loading IQToolbar, IQTitleBarButtonItem, IQBarButtonItem to fix first time keyboard appearance delay (Bug ID: #550)
// If you experience exception breakpoint issue at below line then try these solutions https://stackoverflow.com/questions/27375640/all-exception-break-point-is-stopping-for-no-reason-on-simulator
DispatchQueue.main.async {
let textField = UITextField()
textField.addDoneOnKeyboardWithTarget(nil, action: #selector(self.doneAction(_:)))
textField.addPreviousNextDoneOnKeyboardWithTarget(nil, previousAction: #selector(self.previousAction(_:)), nextAction: #selector(self.nextAction(_:)), doneAction: #selector(self.doneAction(_:)))
}
}
deinit {
@ -389,7 +391,7 @@ Codeless drop-in universal library allows to prevent issues of keyboard sliding
textFieldView.isAlertViewTextField() == false else {
return
}
optimizedAdjustPosition()
self.adjustPosition()
}
}

View File

@ -30,13 +30,28 @@ private final class IQTextFieldViewInfoModal: NSObject {
fileprivate weak var textFieldDelegate: UITextFieldDelegate?
fileprivate weak var textViewDelegate: UITextViewDelegate?
fileprivate weak var textFieldView: UIView?
fileprivate var originalReturnKeyType = UIReturnKeyType.default
fileprivate let originalReturnKeyType: UIReturnKeyType
init(textFieldView: UIView?, textFieldDelegate: UITextFieldDelegate?, textViewDelegate: UITextViewDelegate?, originalReturnKeyType: UIReturnKeyType = .default) {
self.textFieldView = textFieldView
self.textFieldDelegate = textFieldDelegate
self.textViewDelegate = textViewDelegate
self.originalReturnKeyType = originalReturnKeyType
init(textField: UITextField) {
self.textFieldView = textField
self.textFieldDelegate = textField.delegate
self.originalReturnKeyType = textField.returnKeyType
}
init(textView: UITextView) {
self.textFieldView = textView
self.textViewDelegate = textView.delegate
self.originalReturnKeyType = textView.returnKeyType
}
func restore() {
if let textField = textFieldView as? UITextField {
textField.returnKeyType = originalReturnKeyType
textField.delegate = textFieldDelegate
} else if let textView = textFieldView as? UITextView {
textView.returnKeyType = originalReturnKeyType
textView.delegate = textViewDelegate
}
}
}
@ -60,9 +75,9 @@ Manages the return key to work like next/done in a view hierarchy.
didSet {
for modal in textFieldInfoCache {
for model in textFieldInfoCache {
if let view = modal.textFieldView {
if let view = model.textFieldView {
updateReturnKeyTypeOnTextField(view)
}
}
@ -86,20 +101,9 @@ Manages the return key to work like next/done in a view hierarchy.
deinit {
for modal in textFieldInfoCache {
if let textField = modal.textFieldView as? UITextField {
textField.returnKeyType = modal.originalReturnKeyType
textField.delegate = modal.textFieldDelegate
} else if let textView = modal.textFieldView as? UITextView {
textView.returnKeyType = modal.originalReturnKeyType
textView.delegate = modal.textViewDelegate
}
}
// for model in textFieldInfoCache {
// model.restore()
// }
textFieldInfoCache.removeAll()
}
@ -110,12 +114,12 @@ Manages the return key to work like next/done in a view hierarchy.
// MARK: Private Functions
private func textFieldViewCachedInfo(_ textField: UIView) -> IQTextFieldViewInfoModal? {
for modal in textFieldInfoCache {
for model in textFieldInfoCache {
if let view = modal.textFieldView {
if let view = model.textFieldView {
if view == textField {
return modal
return model
}
}
}
@ -178,22 +182,16 @@ Manages the return key to work like next/done in a view hierarchy.
*/
@objc public func addTextFieldView(_ view: UIView) {
let modal = IQTextFieldViewInfoModal(textFieldView: view, textFieldDelegate: nil, textViewDelegate: nil)
if let textField = view as? UITextField {
modal.originalReturnKeyType = textField.returnKeyType
modal.textFieldDelegate = textField.delegate
let model = IQTextFieldViewInfoModal(textField: textField)
textFieldInfoCache.append(model)
textField.delegate = self
} else if let textView = view as? UITextView {
modal.originalReturnKeyType = textView.returnKeyType
modal.textViewDelegate = textView.delegate
let model = IQTextFieldViewInfoModal(textView: textView)
textFieldInfoCache.append(model)
textView.delegate = self
}
textFieldInfoCache.append(modal)
}
/**
@ -203,20 +201,10 @@ Manages the return key to work like next/done in a view hierarchy.
*/
@objc public func removeTextFieldView(_ view: UIView) {
if let modal = textFieldViewCachedInfo(view) {
if let textField = view as? UITextField {
textField.returnKeyType = modal.originalReturnKeyType
textField.delegate = modal.textFieldDelegate
} else if let textView = view as? UITextView {
textView.returnKeyType = modal.originalReturnKeyType
textView.delegate = modal.textViewDelegate
}
if let model = textFieldViewCachedInfo(view) {
model.restore()
if let index = textFieldInfoCache.firstIndex(where: { $0.textFieldView == view}) {
textFieldInfoCache.remove(at: index)
}
}
@ -344,8 +332,8 @@ extension IQKeyboardReturnKeyHandler: UITextFieldDelegate {
if aDelegate == nil {
if let modal = textFieldViewCachedInfo(textField) {
aDelegate = modal.textFieldDelegate
if let model = textFieldViewCachedInfo(textField) {
aDelegate = model.textFieldDelegate
}
}
@ -358,23 +346,22 @@ extension IQKeyboardReturnKeyHandler: UITextFieldDelegate {
if aDelegate == nil {
if let modal = textFieldViewCachedInfo(textField) {
aDelegate = modal.textFieldDelegate
if let model = textFieldViewCachedInfo(textField) {
aDelegate = model.textFieldDelegate
}
}
aDelegate?.textFieldDidEndEditing?(textField)
}
@available(iOS 10.0, *)
@objc public func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) {
var aDelegate: UITextFieldDelegate? = delegate
if aDelegate == nil {
if let modal = textFieldViewCachedInfo(textField) {
aDelegate = modal.textFieldDelegate
if let model = textFieldViewCachedInfo(textField) {
aDelegate = model.textFieldDelegate
}
}
@ -469,8 +456,8 @@ extension IQKeyboardReturnKeyHandler: UITextViewDelegate {
if aDelegate == nil {
if let modal = textFieldViewCachedInfo(textView) {
aDelegate = modal.textViewDelegate
if let model = textFieldViewCachedInfo(textView) {
aDelegate = model.textViewDelegate
}
}
@ -483,8 +470,8 @@ extension IQKeyboardReturnKeyHandler: UITextViewDelegate {
if aDelegate == nil {
if let modal = textFieldViewCachedInfo(textView) {
aDelegate = modal.textViewDelegate
if let model = textFieldViewCachedInfo(textView) {
aDelegate = model.textViewDelegate
}
}
@ -517,8 +504,8 @@ extension IQKeyboardReturnKeyHandler: UITextViewDelegate {
if aDelegate == nil {
if let modal = textFieldViewCachedInfo(textView) {
aDelegate = modal.textViewDelegate
if let model = textFieldViewCachedInfo(textView) {
aDelegate = model.textViewDelegate
}
}
@ -531,15 +518,14 @@ extension IQKeyboardReturnKeyHandler: UITextViewDelegate {
if aDelegate == nil {
if let modal = textFieldViewCachedInfo(textView) {
aDelegate = modal.textViewDelegate
if let model = textFieldViewCachedInfo(textView) {
aDelegate = model.textViewDelegate
}
}
aDelegate?.textViewDidChangeSelection?(textView)
}
@available(iOS 10.0, *)
@objc public func textView(_ aTextView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
if delegate == nil {
@ -554,7 +540,6 @@ extension IQKeyboardReturnKeyHandler: UITextViewDelegate {
return true
}
@available(iOS 10.0, *)
@objc public func textView(_ aTextView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
if delegate == nil {
@ -598,8 +583,11 @@ extension IQKeyboardReturnKeyHandler: UITextViewDelegate {
return true
}
}
#if swift(>=5.7)
@available(iOSApplicationExtension, unavailable)
extension IQKeyboardReturnKeyHandler {
@available(iOS 16.0, *)
public func textView(_ aTextView: UITextView, editMenuForTextIn range: NSRange, suggestedActions: [UIMenuElement]) -> UIMenu? {
if delegate == nil {
@ -620,8 +608,8 @@ extension IQKeyboardReturnKeyHandler: UITextViewDelegate {
if aDelegate == nil {
if let modal = textFieldViewCachedInfo(aTextView) {
aDelegate = modal.textViewDelegate
if let model = textFieldViewCachedInfo(aTextView) {
aDelegate = model.textViewDelegate
}
}
@ -634,12 +622,74 @@ extension IQKeyboardReturnKeyHandler: UITextViewDelegate {
if aDelegate == nil {
if let modal = textFieldViewCachedInfo(aTextView) {
aDelegate = modal.textViewDelegate
if let model = textFieldViewCachedInfo(aTextView) {
aDelegate = model.textViewDelegate
}
}
aDelegate?.textView?(aTextView, willDismissEditMenuWith: animator)
}
#endif
}
#endif
#if swift(>=5.9)
@available(iOSApplicationExtension, unavailable)
extension IQKeyboardReturnKeyHandler {
@available(iOS 17.0, *)
public func textView(_ aTextView: UITextView, primaryActionFor textItem: UITextItem, defaultAction: UIAction) -> UIAction? {
if delegate == nil {
if let unwrapDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate {
if unwrapDelegate.responds(to: #selector(textView as (UITextView, UITextItem, UIAction) -> UIAction?)) {
return unwrapDelegate.textView?(aTextView, primaryActionFor: textItem, defaultAction: defaultAction)
}
}
}
return nil
}
@available(iOS 17.0, *)
public func textView(_ aTextView: UITextView, menuConfigurationFor textItem: UITextItem, defaultMenu: UIMenu) -> UITextItem.MenuConfiguration? {
if delegate == nil {
if let unwrapDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate {
if unwrapDelegate.responds(to: #selector(textView as (UITextView, UITextItem, UIMenu) -> UITextItem.MenuConfiguration?)) {
return unwrapDelegate.textView?(aTextView, menuConfigurationFor: textItem, defaultMenu: defaultMenu)
}
}
}
return nil
}
@available(iOS 17.0, *)
public func textView(_ textView: UITextView, textItemMenuWillDisplayFor textItem: UITextItem, animator: UIContextMenuInteractionAnimating) {
var aDelegate: UITextViewDelegate? = delegate
if aDelegate == nil {
if let model = textFieldViewCachedInfo(textView) {
aDelegate = model.textViewDelegate
}
}
aDelegate?.textView?(textView, textItemMenuWillDisplayFor: textItem, animator: animator)
}
@available(iOS 17.0, *)
public func textView(_ textView: UITextView, textItemMenuWillEndFor textItem: UITextItem, animator: UIContextMenuInteractionAnimating) {
var aDelegate: UITextViewDelegate? = delegate
if aDelegate == nil {
if let model = textFieldViewCachedInfo(textView) {
aDelegate = model.textViewDelegate
}
}
aDelegate?.textView?(textView, textItemMenuWillEndFor: textItem, animator: animator)
}
}
#endif

View File

@ -0,0 +1,38 @@
//
// IQPlaceholderable.swift
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-20 Iftekhar Qurashi.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
import UIKit
@available(iOSApplicationExtension, unavailable)
public protocol IQPlaceholderable: AnyObject {
var placeholder: String? { get set }
var attributedPlaceholder: NSAttributedString? { get set }
}
@available(iOSApplicationExtension, unavailable)
extension UITextField: IQPlaceholderable { }
@available(iOSApplicationExtension, unavailable)
extension IQTextView: IQPlaceholderable { }

View File

@ -42,10 +42,6 @@ import UIKit
NotificationCenter.default.addObserver(self, selector: #selector(self.refreshPlaceholder), name: UITextView.textDidChangeNotification, object: self)
}
deinit {
IQ_PlaceholderLabel.removeFromSuperview()
}
private var placeholderInsets: UIEdgeInsets {
return UIEdgeInsets(top: self.textContainerInset.top, left: self.textContainerInset.left + self.textContainer.lineFragmentPadding, bottom: self.textContainerInset.bottom, right: self.textContainerInset.right + self.textContainer.lineFragmentPadding)
}
@ -53,12 +49,12 @@ import UIKit
private var placeholderExpectedFrame: CGRect {
let placeholderInsets = self.placeholderInsets
let maxWidth = self.frame.width-placeholderInsets.left-placeholderInsets.right
let expectedSize = IQ_PlaceholderLabel.sizeThatFits(CGSize(width: maxWidth, height: self.frame.height-placeholderInsets.top-placeholderInsets.bottom))
let expectedSize = placeholderLabel.sizeThatFits(CGSize(width: maxWidth, height: self.frame.height-placeholderInsets.top-placeholderInsets.bottom))
return CGRect(x: placeholderInsets.left, y: placeholderInsets.top, width: maxWidth, height: expectedSize.height)
}
lazy var IQ_PlaceholderLabel: UILabel = {
lazy var placeholderLabel: UILabel = {
let label = UILabel()
label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
@ -83,11 +79,11 @@ import UIKit
@IBInspectable open var placeholderTextColor: UIColor? {
get {
return IQ_PlaceholderLabel.textColor
return placeholderLabel.textColor
}
set {
IQ_PlaceholderLabel.textColor = newValue
placeholderLabel.textColor = newValue
}
}
@ -95,11 +91,11 @@ import UIKit
@IBInspectable open var placeholder: String? {
get {
return IQ_PlaceholderLabel.text
return placeholderLabel.text
}
set {
IQ_PlaceholderLabel.text = newValue
placeholderLabel.text = newValue
refreshPlaceholder()
}
}
@ -107,11 +103,11 @@ import UIKit
/** @abstract To set textView's placeholder attributed text. Default is nil. */
open var attributedPlaceholder: NSAttributedString? {
get {
return IQ_PlaceholderLabel.attributedText
return placeholderLabel.attributedText
}
set {
IQ_PlaceholderLabel.attributedText = newValue
placeholderLabel.attributedText = newValue
refreshPlaceholder()
}
}
@ -119,15 +115,16 @@ import UIKit
@objc override open func layoutSubviews() {
super.layoutSubviews()
IQ_PlaceholderLabel.frame = placeholderExpectedFrame
placeholderLabel.frame = placeholderExpectedFrame
}
@objc internal func refreshPlaceholder() {
if !text.isEmpty || !attributedText.string.isEmpty {
IQ_PlaceholderLabel.alpha = 0
let text: String = text ?? attributedText?.string ?? ""
if text.isEmpty {
placeholderLabel.alpha = 1
} else {
IQ_PlaceholderLabel.alpha = 1
placeholderLabel.alpha = 0
}
}
@ -150,16 +147,16 @@ import UIKit
didSet {
if let unwrappedFont = font {
IQ_PlaceholderLabel.font = unwrappedFont
placeholderLabel.font = unwrappedFont
} else {
IQ_PlaceholderLabel.font = UIFont.systemFont(ofSize: 12)
placeholderLabel.font = UIFont.systemFont(ofSize: 12)
}
}
}
@objc override open var textAlignment: NSTextAlignment {
didSet {
IQ_PlaceholderLabel.textAlignment = textAlignment
placeholderLabel.textAlignment = textAlignment
}
}

View File

@ -109,8 +109,4 @@ import UIKit
}
}
}
deinit {
target = nil
}
}

View File

@ -135,8 +135,4 @@ import UIKit
@objc required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
deinit {
customView = nil
}
}

View File

@ -139,12 +139,7 @@ import UIKit
privateFixedSpaceBarButton = IQBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
}
privateFixedSpaceBarButton!.isSystemItem = true
if #available(iOS 10, *) {
privateFixedSpaceBarButton!.width = 6
} else {
privateFixedSpaceBarButton!.width = 20
}
return privateFixedSpaceBarButton!
}

View File

@ -68,53 +68,7 @@ import UIKit
@available(iOSApplicationExtension, unavailable)
@objc public extension UIImage {
static func keyboardLeftImage() -> UIImage? {
struct Static {
static var keyboardLeftImage: UIImage?
}
if Static.keyboardLeftImage == nil {
// swiftlint:disable line_length
let base64Data = "iVBORw0KGgoAAAANSUhEUgAAACQAAAA/CAYAAACIEWrAAAAAAXNSR0IArs4c6QAABtFJREFUaAXFmV1oHFUUx++d3SSbj/0k6Uc2u7Ob7QeVSqBSP7AUm1JpS0tb+6nFYhELxfahDxVU9KmgD0UU7ENRLLRQodRqNbVJY5IGXwRBEPHBh2x2ZpPQaDC7W2qSzc5c/3ebDTN3d5Pd7Gw6L3PPOcM5vzn33I+5Q8gTvJqbm52RYPAdIEg5DFuusdz3dq/X7XA6ewiVTvrcnvBkMvE9GNgTAQoGg16pztFLKX02mwhKOrwe99rJZPL2sgO1tbX5aiWpDzDPGHuFEvq01+2ZpEZltdutra3NjpranxC0Q4zFCLsVVZRjdtFQLTmycuUKZq/pA8zGvBiM3IiqynHoM8sCFGoJrSIO1o9u2SDCIDPXAXMCeo3bqg4UCARaJYkMEELXiTCEkauAOQm9nrPNj/+cwso7aiZQS6VBdFMeDDLz1ZAaM8Hw2FXLUHj1apnaawYIpWHxJRkjl5GZ09Az0VYVIFmWw6iXAWRGFgMynV2KxpWzhWD4s5Z3GeaZNXZGeTflwzDyGWDOFIPhQJZmqN3vX0clG7qJtHLnpktnFwFz3qQrIFgGJK+WN+D1+jGaVolxGNM/jsbVd0V9IdkSoEggsJFJlE96K8Qgus4uDMfVD0R9MbniGgr7/R1YsXkB58FgEH04HFdKhuGQFWUIo2kTZaQXQ9snvjGG9nsY2h+J+sXkJQO1BwKbMYv0YNX2ikF0ws4Pq8pFUV+KvCSgkD/0PCaMbnSTWwyCzJwDzKeivlS5bCBsOV/EsL6LAE5jEMYvSs4C5pJRX267LKBwILAVw/oOgjQZAz1mYaejinrZqF9Ku+QdY0SWOzkMaqbRGAgwOjJzKqqqXxj1S22jDBa/wsHgDqxNtwFTb3w6C0PYyWFVvWrUV9JetMsibfIuRuktkDuMgQCjYRdzYnhEvW7UV9peEKg9GNyDOeYmYOpMgRjLYD9zHDA3THoLhKIzdSgQ2k+p9A1imGEImUXNHEM3WQ7D36dghlAzhyRKeFfU8IcMV1rTtSOxePy2QWdpMw8oEggdwxp0DVFE2wy66SBg+LCv2mUa9mFZfhORrmA0mWCwz5zWdW0/uolPiFW95msIMGckQr8EjAkSo2mKMH0vMtNTVZI559lMtAdC5zCSPhEDAuaRppG9yqg6INqqJVNk5m1k5nMxAGAYYLYro8qywXAGiWYyvYSxUREIXUdtdnIKelM9ic9ZLWeXDnxdRmppdnMeEAMgUTex0XoN+lnRVg05C8Qd828pW5FvKUwD3w0pylE8lq4GhNHnPBBX+v3+tjpbTT+lZK3xId5GprqQqUNozog2K2UTEHfMDwdqJBtOKsh6MRAmxru6Ql+Jkdi0aLNKzgPijvnxia2e9WFhfUoMhC1qb1rP7BsZGZkSbVbI8xOj0Vnsn9gDMjO9DcH/MOp5G925o1aydeFko0G0WSEXBOKOh8bH/57OpDuxbPwuBsKM0Omw195taWkxbWXF55YiFwXizsbGxibSWqYTFf2b6ByZ2uqsb+jmZ82irRK5YA2JDkOekEdykXuA2CzaMP5+YanUzujkZDLfVr6mJCDu1ufzubxOZzeq6AUxDGrtVz1FXo4lYgnRVq5cMhB3zLvH1dD4I2poS14gdOuMru3A6Ps3z1aGYsEaEv1MTEw8fDQzvRP6QdGG4bep1mbv52fRebYyFGUBcb/j4+OPpmbTuzFz4yzIfCHdHQ6cK/IzabOldKlsIO4ao++/tK7tQe3cE0OhOzcSh+N+9mxaNJYgl1VDBfzVtcsyvtnobtGG+euvWV3rjMfjY6JtIXlJGTI4nMH/iQPI1A8GXbaJN13Pz6j5gi3aFpIrBeK+01E1dhAL77d5gShd47DZB/mZdZ6tiKLSLjO6tUeCoes4qjlsVPI2uk/RCNumKMqwaBNlKzKU85nBr4JXkamvc4rcHW8t87NrvjPN6YrdrQTiMTTU1OtY+67lBaQk+9+Dn2Xn2QwKq4G4a21IVd5Apq4Y4jxuUuonNvv97Jl2nnHukSJ6K9Q0EpQvYwZ/S3SGmhrPMH27qqp/ijbTV6porFTGT90u/NxdgXnKtEtATTXZKD3scTb1JFKpcWOcqgLxQIC643F7fNi6PGcMjHYjZvUjrkZPb/Jh8kHOVnUgHiiRTHQjUy5kyrx1obSBSuSI1+Xqm0ylsjP6sgBxKGTqHn6D1yNTpq0LslSPXxNH3c6mAXTfqJUTI4+76IXT3AvY5L1f4MFUhrBdy5ahHAAy1e91uzD46Es53dydYv7qWnYgHhxQgx6XexZQ2+dgZojGDuCf2p0nAsQhEqnkzz63awpz0hacve+LjqjZA7H/AWSbJ/TPf3CuAAAAAElFTkSuQmCC"
// swiftlint:enable line_length
if let data = Data(base64Encoded: base64Data, options: .ignoreUnknownCharacters) {
Static.keyboardLeftImage = UIImage(data: data, scale: 3)
}
// Support for RTL languages like Arabic, Persia etc... (Bug ID: #448)
Static.keyboardLeftImage = Static.keyboardLeftImage?.imageFlippedForRightToLeftLayoutDirection()
}
return Static.keyboardLeftImage
}
static func keyboardRightImage() -> UIImage? {
struct Static {
static var keyboardRightImage: UIImage?
}
if Static.keyboardRightImage == nil {
// swiftlint:disable line_length
let base64Data = "iVBORw0KGgoAAAANSUhEUgAAACQAAAA/CAYAAACIEWrAAAAAAXNSR0IArs4c6QAABu5JREFUaAXFmXtsFEUcx2f3rj0Kvd29k9LHtXfXqyjGV2J8EF/hIQgp4VnahPgIxviH0ZgYNSbGmBg1McaYGGOM+o8k+EINMQjIo6UoBAVEEBGQXnvbS1ttw91epUDbu/E7lb3bm22Pu97uOQnszO+3ne/nvjM7sw9CMsXRFAi83jhnTnUmVPqacEXSGfIHPhMEoYUSejpJyKJIJNJfehxCRIiWwZktDIYBCESY56BCZ319ve9/AQr5/c8CY7VRXBDIXJfo6Kyrq2swxktRZ0NWFgoEPocza3lBDF9P6rKwsGegp4fP2dVmQzYWjkTaCCVf8iKADIou0un3+0N8zq42A2JlvEvt2QBHPv2vmfkfFvrLiNAZqq+fm4naV9OBmEISTj0MpzaZ5AShXhAd+xrr6q435SwO6Je9sVsRc+ojDNdjxiCrw8GBcUoXq6p6is9Z1TY6pPeZglOPQ/1DPaAfAVnjFMQODN/Neszqo2OqDmNa/DuPJM/G+nSn8RxYOgux9Upl5a748PBfxpwV9SmBWOexhLbdIyserEvzs8QEYSYRxFZJUfZommbpip4TaAJKi+/0SnIlEYS7jVBwqQJutXkkqT2WSPQZc8XUrwo0AZXQdntkaQYg7jWKYU4hJrZJlXKnNqxFjbnp1vMCmoDStL2KJDsBdT8n5hJFoRXAP8Q0TeVyBTfzBmI9xxNah1eRU9j7FnJKLrTbZLf7QDyRiHC5gpoFAbGe4cJ+TPRRTPTFRiU4V45/rV5FOYRzuo25QuoFA7HOsST8qCjyBcyhpUYxAJVRSloVSToMp7qMuXzr0wJincc17SCc0uDUMqMYg8JEb/W65aNYNs4Zc/nUpw3EOodTh+DUEFb15QDBKpAuTiJi8ZSl4wA/m47mUSkKiPUPwcNeWR6ghDRzUA60W+DUSTh1Og+WiVOKBmK9YBIfVRQlCqdW8FC4J16nyPJpgOe1IVsCxKAgeAxOReDUyiwoTCik13olz9lYIn6SnZurWAbERODUcY+idMGpVYBK30mwOm5d1sCpMMBPlAzoCtRvsiSdEdmDAweF/Go4pcKpX6eCstQhXQRr0O9w6hTWqTWIpTXYUMKpVXCqD079op9vPKZPNgatqGP4/pAl9wlRENnTTFqHQaG9wiN5/oZTR3it9Il8woo2nDrjUeRjcGod+nPqfTIoYDVjnToPp37W4+xoKxATgFN/ym7lCKZ4C6xJQ7EcqJZjsx7BOQdZmxXbgZhIPBE/h9uTn1BdD4gyFssUYQmgkoDaz2IlAWJCEAxLlcpBDFULoMpZLFOERdgXBWxF+4z7TyZvYy1YH1wginQvoNLrlC6XIvT5rDHVEzYeRYdINhrXJ10LK7yapPSbUgI58AC6CQAbdAj9SCntpmOjC9X+/kipgJxN/uBmALTqEOkjpecujY8t6uvv72WxUgBNvO6B1iSve8jxkdHLSwYGBgZ1QLuByuHMFoit1AUzR3psNJl8ADDnMzF7HXLhveXXuB9qNgqyOubMkXFCl0aj0Rifs8WhIAnOcPjJVsA8yAsC5xAZTixTYzHNnLPBIbwsrcA68y0u7Qd4QThzIDFyYflQLDbM5/S2pQ5VV1fPcjkc27BLLdAF9CMej/YPXxxpHhoa+kePTXa0DKiqqqpylqtiO0TuMwvRDlzaKwYHB0fMueyIJUBer1eSKmbuwJzJekPCpODM7tFUclVfX9/FbOnJW0UDhTwembil79H9XWYJujOlCmuiJHrJnJs8UhQQXhd7MF92YYe+ne8eE3hbWI20IH6Zz+Vqm3bcXCcbcz6f7xo8M7Nd2wSDgdoKGHaXWBAM639aDtXU1FS5nGV78Pe3sE6MBc58BRi2gY4Z4/nWCwZin6/EctdeCNxoEqHkC8A8hPi4KZdnoCCgQCBQi/nSjnkzj+8fzmwGzKOIJ/lcIe285xD7XOUgwj48QZhgUpR8AphHioVh4HkBsc9U7HMV3LnO9Gsp/bhb7dmIOF71FV+uOmSNtbUBwVnWgb2pkZejNPVBWFWfRBx3oNaUnEDssxTuxdvhTMAkl6LvhXvVp03xIgNTDhnmzLXss9RkMHg+f6erN2I5DPstkzrEPkOJoqMdw1TH/+AUpW91q5EX+LhVbRNQoDZwA54t2aVdYxahbwDmJXPcukgWUFNDw01UxHZAyBxeArv2q7i0X+HjVrfTQI0+3634wrMHMLPNIvRlwLxmjlsfmQDCCnwb3iTtxpzx8hK4tF/Epf0mH7er7Qw1NNyBzndh11Z4kVSKPtfdq77Nx+1sO7GiVeCNpBN3e9mFpp4BzLvZQftbExhNfv89mD87IOfGJollhjwV7o28b798DoWgLzgfD3bnAfdEjtNsT/0LGvgrBSkuN9gAAAAASUVORK5CYII="
// swiftlint:enable line_length
if let data = Data(base64Encoded: base64Data, options: .ignoreUnknownCharacters) {
Static.keyboardRightImage = UIImage(data: data, scale: 3)
}
// Support for RTL languages like Arabic, Persia etc... (Bug ID: #448)
Static.keyboardRightImage = Static.keyboardRightImage?.imageFlippedForRightToLeftLayoutDirection()
}
return Static.keyboardRightImage
}
static func keyboardUpImage() -> UIImage? {
static func keyboardPreviousImage() -> UIImage? {
struct Static {
static var keyboardUpImage: UIImage?
@ -137,7 +91,7 @@ import UIKit
return Static.keyboardUpImage
}
static func keyboardDownImage() -> UIImage? {
static func keyboardNextImage() -> UIImage? {
struct Static {
static var keyboardDownImage: UIImage?
@ -159,24 +113,6 @@ import UIKit
return Static.keyboardDownImage
}
static func keyboardPreviousImage() -> UIImage? {
if #available(iOS 10, *) {
return keyboardUpImage()
} else {
return keyboardLeftImage()
}
}
static func keyboardNextImage() -> UIImage? {
if #available(iOS 10, *) {
return keyboardDownImage()
} else {
return keyboardRightImage()
}
}
}
/**
@ -207,7 +143,15 @@ UIView category methods to add IQToolbar on UIKeyboard.
return unwrappedToolbar
} else {
let frame = CGRect(origin: .zero, size: .init(width: UIScreen.main.bounds.width, height: 44))
var width: CGFloat = 0
if #available(iOS 13.0, *) {
width = window?.windowScene?.screen.bounds.width ?? .zero
} else {
width = UIScreen.main.bounds.width
}
let frame = CGRect(origin: .zero, size: .init(width: width, height: 44))
let newToolbar = IQToolbar(frame: frame)
objc_setAssociatedObject(self, &AssociatedKeys.keyboardToolbar, newToolbar, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
@ -253,12 +197,13 @@ UIView category methods to add IQToolbar on UIKeyboard.
return nil
} else if self.toolbarPlaceholder?.isEmpty == false {
return self.toolbarPlaceholder
} else if self.responds(to: #selector(getter: UITextField.placeholder)) {
} else if let placeholderable: IQPlaceholderable = self as? IQPlaceholderable {
if let textField = self as? UITextField {
return textField.placeholder
} else if let textView = self as? IQTextView {
return textView.placeholder
if let placeholder = placeholderable.attributedPlaceholder?.string,
!placeholder.isEmpty {
return placeholder
} else if let placeholder = placeholderable.placeholder {
return placeholder
} else {
return nil
}

19
Pods/MJRefresh/LICENSE generated Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2013-2015 MJRefresh (https://github.com/CoderMJLee/MJRefresh)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,34 @@
//
// MJRefreshAutoFooter.h
// MJRefresh
//
// Created by MJ Lee on 15/4/24.
// Copyright (c) 2015年 小码哥. All rights reserved.
//
#if __has_include(<MJRefresh/MJRefreshFooter.h>)
#import <MJRefresh/MJRefreshFooter.h>
#else
#import "MJRefreshFooter.h"
#endif
NS_ASSUME_NONNULL_BEGIN
@interface MJRefreshAutoFooter : MJRefreshFooter
/** 是否自动刷新(默认为YES) */
@property (assign, nonatomic, getter=isAutomaticallyRefresh) BOOL automaticallyRefresh;
/** 当底部控件出现多少时就自动刷新(默认为1.0,也就是底部控件完全出现时,才会自动刷新) */
@property (assign, nonatomic) CGFloat appearencePercentTriggerAutoRefresh MJRefreshDeprecated("请使用triggerAutomaticallyRefreshPercent属性");
/** 当底部控件出现多少时就自动刷新(默认为1.0,也就是底部控件完全出现时,才会自动刷新) */
@property (assign, nonatomic) CGFloat triggerAutomaticallyRefreshPercent;
/** 自动触发次数, 默认为 1, 仅在拖拽 ScrollView 时才生效,
-1,
*/
@property (nonatomic) NSInteger autoTriggerTimes;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,216 @@
//
// MJRefreshAutoFooter.m
// MJRefresh
//
// Created by MJ Lee on 15/4/24.
// Copyright (c) 2015 . All rights reserved.
//
#import "MJRefreshAutoFooter.h"
#import "NSBundle+MJRefresh.h"
#import "UIView+MJExtension.h"
#import "UIScrollView+MJExtension.h"
#import "UIScrollView+MJRefresh.h"
@interface MJRefreshAutoFooter()
/** */
@property (nonatomic) BOOL triggerByDrag;
@property (nonatomic) NSInteger leftTriggerTimes;
@end
@implementation MJRefreshAutoFooter
#pragma mark -
- (void)willMoveToSuperview:(UIView *)newSuperview
{
[super willMoveToSuperview:newSuperview];
if (newSuperview) { //
if (self.hidden == NO) {
self.scrollView.mj_insetB += self.mj_h;
}
//
self.mj_y = _scrollView.mj_contentH + self.ignoredScrollViewContentInsetBottom;
} else { //
if (self.hidden == NO) {
self.scrollView.mj_insetB -= self.mj_h;
}
}
}
#pragma mark -
- (void)setAppearencePercentTriggerAutoRefresh:(CGFloat)appearencePercentTriggerAutoRefresh
{
self.triggerAutomaticallyRefreshPercent = appearencePercentTriggerAutoRefresh;
}
- (CGFloat)appearencePercentTriggerAutoRefresh
{
return self.triggerAutomaticallyRefreshPercent;
}
#pragma mark -
- (void)prepare
{
[super prepare];
// 100%
self.triggerAutomaticallyRefreshPercent = 1.0;
//
self.automaticallyRefresh = YES;
self.autoTriggerTimes = 1;
}
- (void)scrollViewContentSizeDidChange:(NSDictionary *)change
{
[super scrollViewContentSizeDidChange:change];
CGSize size = [change[NSKeyValueChangeNewKey] CGSizeValue];
CGFloat contentHeight = size.height == 0 ? self.scrollView.mj_contentH : size.height;
//
CGFloat y = contentHeight + self.ignoredScrollViewContentInsetBottom;
if (self.mj_y != y) {
self.mj_y = y;
}
}
- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change
{
[super scrollViewContentOffsetDidChange:change];
if (self.state != MJRefreshStateIdle || !self.automaticallyRefresh || self.mj_y == 0) return;
if (_scrollView.mj_insetT + _scrollView.mj_contentH > _scrollView.mj_h) { //
// _scrollView.mj_contentHself.mj_y
if (_scrollView.mj_offsetY >= _scrollView.mj_contentH - _scrollView.mj_h + self.mj_h * self.triggerAutomaticallyRefreshPercent + _scrollView.mj_insetB - self.mj_h) {
//
CGPoint old = [change[@"old"] CGPointValue];
CGPoint new = [change[@"new"] CGPointValue];
if (new.y <= old.y) return;
if (_scrollView.isDragging) {
self.triggerByDrag = YES;
}
//
[self beginRefreshing];
}
}
}
- (void)scrollViewPanStateDidChange:(NSDictionary *)change
{
[super scrollViewPanStateDidChange:change];
if (self.state != MJRefreshStateIdle) return;
UIGestureRecognizerState panState = _scrollView.panGestureRecognizer.state;
switch (panState) {
//
case UIGestureRecognizerStateEnded: {
if (_scrollView.mj_insetT + _scrollView.mj_contentH <= _scrollView.mj_h) { //
if (_scrollView.mj_offsetY >= - _scrollView.mj_insetT) { //
self.triggerByDrag = YES;
[self beginRefreshing];
}
} else { //
if (_scrollView.mj_offsetY >= _scrollView.mj_contentH + _scrollView.mj_insetB - _scrollView.mj_h) {
self.triggerByDrag = YES;
[self beginRefreshing];
}
}
}
break;
case UIGestureRecognizerStateBegan: {
[self resetTriggerTimes];
}
break;
default:
break;
}
}
- (BOOL)unlimitedTrigger {
return self.leftTriggerTimes == -1;
}
- (void)beginRefreshing
{
if (self.triggerByDrag && self.leftTriggerTimes <= 0 && !self.unlimitedTrigger) {
return;
}
[super beginRefreshing];
}
- (void)setState:(MJRefreshState)state
{
MJRefreshCheckState
if (state == MJRefreshStateRefreshing) {
[self executeRefreshingCallback];
} else if (state == MJRefreshStateNoMoreData || state == MJRefreshStateIdle) {
if (self.triggerByDrag) {
if (!self.unlimitedTrigger) {
self.leftTriggerTimes -= 1;
}
self.triggerByDrag = NO;
}
if (MJRefreshStateRefreshing == oldState) {
if (self.scrollView.pagingEnabled) {
CGPoint offset = self.scrollView.contentOffset;
offset.y -= self.scrollView.mj_insetB;
[UIView animateWithDuration:self.slowAnimationDuration animations:^{
self.scrollView.contentOffset = offset;
if (self.endRefreshingAnimationBeginAction) {
self.endRefreshingAnimationBeginAction();
}
} completion:^(BOOL finished) {
if (self.endRefreshingCompletionBlock) {
self.endRefreshingCompletionBlock();
}
}];
return;
}
if (self.endRefreshingCompletionBlock) {
self.endRefreshingCompletionBlock();
}
}
}
}
- (void)resetTriggerTimes {
self.leftTriggerTimes = self.autoTriggerTimes;
}
- (void)setHidden:(BOOL)hidden
{
BOOL lastHidden = self.isHidden;
[super setHidden:hidden];
if (!lastHidden && hidden) {
self.state = MJRefreshStateIdle;
self.scrollView.mj_insetB -= self.mj_h;
} else if (lastHidden && !hidden) {
self.scrollView.mj_insetB += self.mj_h;
//
self.mj_y = _scrollView.mj_contentH + self.ignoredScrollViewContentInsetBottom;
}
}
- (void)setAutoTriggerTimes:(NSInteger)autoTriggerTimes {
_autoTriggerTimes = autoTriggerTimes;
self.leftTriggerTimes = autoTriggerTimes;
}
@end

View File

@ -0,0 +1,21 @@
//
// MJRefreshBackFooter.h
// MJRefresh
//
// Created by MJ Lee on 15/4/24.
// Copyright (c) 2015年 小码哥. All rights reserved.
//
#if __has_include(<MJRefresh/MJRefreshFooter.h>)
#import <MJRefresh/MJRefreshFooter.h>
#else
#import "MJRefreshFooter.h"
#endif
NS_ASSUME_NONNULL_BEGIN
@interface MJRefreshBackFooter : MJRefreshFooter
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,158 @@
//
// MJRefreshBackFooter.m
// MJRefresh
//
// Created by MJ Lee on 15/4/24.
// Copyright (c) 2015 . All rights reserved.
//
#import "MJRefreshBackFooter.h"
#import "NSBundle+MJRefresh.h"
#import "UIView+MJExtension.h"
#import "UIScrollView+MJExtension.h"
#import "UIScrollView+MJRefresh.h"
@interface MJRefreshBackFooter()
@property (assign, nonatomic) NSInteger lastRefreshCount;
@property (assign, nonatomic) CGFloat lastBottomDelta;
@end
@implementation MJRefreshBackFooter
#pragma mark -
- (void)willMoveToSuperview:(UIView *)newSuperview
{
[super willMoveToSuperview:newSuperview];
[self scrollViewContentSizeDidChange:nil];
}
#pragma mark -
- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change
{
[super scrollViewContentOffsetDidChange:change];
//
if (self.state == MJRefreshStateRefreshing) return;
_scrollViewOriginalInset = self.scrollView.mj_inset;
// contentOffset
CGFloat currentOffsetY = self.scrollView.mj_offsetY;
// offsetY
CGFloat happenOffsetY = [self happenOffsetY];
//
if (currentOffsetY <= happenOffsetY) return;
CGFloat pullingPercent = (currentOffsetY - happenOffsetY) / self.mj_h;
// pullingPercent
if (self.state == MJRefreshStateNoMoreData) {
self.pullingPercent = pullingPercent;
return;
}
if (self.scrollView.isDragging) {
self.pullingPercent = pullingPercent;
//
CGFloat normal2pullingOffsetY = happenOffsetY + self.mj_h;
if (self.state == MJRefreshStateIdle && currentOffsetY > normal2pullingOffsetY) {
//
self.state = MJRefreshStatePulling;
} else if (self.state == MJRefreshStatePulling && currentOffsetY <= normal2pullingOffsetY) {
//
self.state = MJRefreshStateIdle;
}
} else if (self.state == MJRefreshStatePulling) {// &&
//
[self beginRefreshing];
} else if (pullingPercent < 1) {
self.pullingPercent = pullingPercent;
}
}
- (void)scrollViewContentSizeDidChange:(NSDictionary *)change
{
[super scrollViewContentSizeDidChange:change];
CGSize size = [change[NSKeyValueChangeNewKey] CGSizeValue];
CGFloat contentHeight = size.height == 0 ? self.scrollView.mj_contentH : size.height;
//
contentHeight += self.ignoredScrollViewContentInsetBottom;
//
CGFloat scrollHeight = self.scrollView.mj_h - self.scrollViewOriginalInset.top - self.scrollViewOriginalInset.bottom + self.ignoredScrollViewContentInsetBottom;
//
CGFloat y = MAX(contentHeight, scrollHeight);
if (self.mj_y != y) {
self.mj_y = y;
}
}
- (void)setState:(MJRefreshState)state
{
MJRefreshCheckState
//
if (state == MJRefreshStateNoMoreData || state == MJRefreshStateIdle) {
//
if (MJRefreshStateRefreshing == oldState) {
[UIView animateWithDuration:self.slowAnimationDuration animations:^{
if (self.endRefreshingAnimationBeginAction) {
self.endRefreshingAnimationBeginAction();
}
self.scrollView.mj_insetB -= self.lastBottomDelta;
//
if (self.isAutomaticallyChangeAlpha) self.alpha = 0.0;
} completion:^(BOOL finished) {
self.pullingPercent = 0.0;
if (self.endRefreshingCompletionBlock) {
self.endRefreshingCompletionBlock();
}
}];
}
CGFloat deltaH = [self heightForContentBreakView];
//
if (MJRefreshStateRefreshing == oldState && deltaH > 0 && self.scrollView.mj_totalDataCount != self.lastRefreshCount) {
self.scrollView.mj_offsetY = self.scrollView.mj_offsetY;
}
} else if (state == MJRefreshStateRefreshing) {
//
self.lastRefreshCount = self.scrollView.mj_totalDataCount;
[UIView animateWithDuration:self.fastAnimationDuration animations:^{
CGFloat bottom = self.mj_h + self.scrollViewOriginalInset.bottom;
CGFloat deltaH = [self heightForContentBreakView];
if (deltaH < 0) { // view
bottom -= deltaH;
}
self.lastBottomDelta = bottom - self.scrollView.mj_insetB;
self.scrollView.mj_insetB = bottom;
self.scrollView.mj_offsetY = [self happenOffsetY] + self.mj_h;
} completion:^(BOOL finished) {
[self executeRefreshingCallback];
}];
}
}
#pragma mark -
#pragma mark scrollView view
- (CGFloat)heightForContentBreakView
{
CGFloat h = self.scrollView.frame.size.height - self.scrollViewOriginalInset.bottom - self.scrollViewOriginalInset.top;
return self.scrollView.contentSize.height - h;
}
#pragma mark contentOffset.y
- (CGFloat)happenOffsetY
{
CGFloat deltaH = [self heightForContentBreakView];
if (deltaH > 0) {
return deltaH - self.scrollViewOriginalInset.top;
} else {
return - self.scrollViewOriginalInset.top;
}
}
@end

View File

@ -0,0 +1,151 @@
// 代码地址: https://github.com/CoderMJLee/MJRefresh
// MJRefreshComponent.h
// MJRefresh
//
// Created by MJ Lee on 15/3/4.
// Copyright (c) 2015年 小码哥. All rights reserved.
// 刷新控件的基类
#import <UIKit/UIKit.h>
#if __has_include(<MJRefresh/MJRefreshConst.h>)
#import <MJRefresh/MJRefreshConst.h>
#else
#import "MJRefreshConst.h"
#endif
NS_ASSUME_NONNULL_BEGIN
/** 刷新控件的状态 */
typedef NS_ENUM(NSInteger, MJRefreshState) {
/** 普通闲置状态 */
MJRefreshStateIdle = 1,
/** 松开就可以进行刷新的状态 */
MJRefreshStatePulling,
/** 正在刷新中的状态 */
MJRefreshStateRefreshing,
/** 即将刷新的状态 */
MJRefreshStateWillRefresh,
/** 所有数据加载完毕,没有更多的数据了 */
MJRefreshStateNoMoreData
};
/** 进入刷新状态的回调 */
typedef void (^MJRefreshComponentRefreshingBlock)(void) MJRefreshDeprecated("first deprecated in 3.3.0 - Use `MJRefreshComponentAction` instead");
/** 开始刷新后的回调(进入刷新状态后的回调) */
typedef void (^MJRefreshComponentBeginRefreshingCompletionBlock)(void) MJRefreshDeprecated("first deprecated in 3.3.0 - Use `MJRefreshComponentAction` instead");
/** 结束刷新后的回调 */
typedef void (^MJRefreshComponentEndRefreshingCompletionBlock)(void) MJRefreshDeprecated("first deprecated in 3.3.0 - Use `MJRefreshComponentAction` instead");
/** 刷新用到的回调类型 */
typedef void (^MJRefreshComponentAction)(void);
/** 刷新控件的基类 */
@interface MJRefreshComponent : UIView
{
/** 记录scrollView刚开始的inset */
UIEdgeInsets _scrollViewOriginalInset;
/** 父控件 */
__weak UIScrollView *_scrollView;
}
#pragma mark - 刷新动画时间控制
/** 快速动画时间(一般用在刷新开始的回弹动画), 默认 0.25 */
@property (nonatomic) NSTimeInterval fastAnimationDuration;
/** 慢速动画时间(一般用在刷新结束后的回弹动画), 默认 0.4*/
@property (nonatomic) NSTimeInterval slowAnimationDuration;
/** 关闭全部默认动画效果, 可以简单粗暴地解决 CollectionView 的回弹动画 bug */
- (instancetype)setAnimationDisabled;
#pragma mark - 刷新回调
/** 正在刷新的回调 */
@property (copy, nonatomic, nullable) MJRefreshComponentAction refreshingBlock;
/** 设置回调对象和回调方法 */
- (void)setRefreshingTarget:(id)target refreshingAction:(SEL)action;
/** 回调对象 */
@property (weak, nonatomic) id refreshingTarget;
/** 回调方法 */
@property (assign, nonatomic) SEL refreshingAction;
/** 触发回调(交给子类去调用) */
- (void)executeRefreshingCallback;
#pragma mark - 刷新状态控制
/** 进入刷新状态 */
- (void)beginRefreshing;
- (void)beginRefreshingWithCompletionBlock:(void (^)(void))completionBlock;
/** 开始刷新后的回调(进入刷新状态后的回调) */
@property (copy, nonatomic, nullable) MJRefreshComponentAction beginRefreshingCompletionBlock;
/** 带动画的结束刷新的回调 */
@property (copy, nonatomic, nullable) MJRefreshComponentAction endRefreshingAnimateCompletionBlock MJRefreshDeprecated("first deprecated in 3.3.0 - Use `endRefreshingAnimationBeginAction` instead");
@property (copy, nonatomic, nullable) MJRefreshComponentAction endRefreshingAnimationBeginAction;
/** 结束刷新的回调 */
@property (copy, nonatomic, nullable) MJRefreshComponentAction endRefreshingCompletionBlock;
/** 结束刷新状态 */
- (void)endRefreshing;
- (void)endRefreshingWithCompletionBlock:(void (^)(void))completionBlock;
/** 是否正在刷新 */
@property (assign, nonatomic, readonly, getter=isRefreshing) BOOL refreshing;
/** 刷新状态 一般交给子类内部实现 */
@property (assign, nonatomic) MJRefreshState state;
#pragma mark - 交给子类去访问
/** 记录scrollView刚开始的inset */
@property (assign, nonatomic, readonly) UIEdgeInsets scrollViewOriginalInset;
/** 父控件 */
@property (weak, nonatomic, readonly) UIScrollView *scrollView;
#pragma mark - 交给子类们去实现
/** 初始化 */
- (void)prepare NS_REQUIRES_SUPER;
/** 摆放子控件frame */
- (void)placeSubviews NS_REQUIRES_SUPER;
/** 当scrollView的contentOffset发生改变的时候调用 */
- (void)scrollViewContentOffsetDidChange:(nullable NSDictionary *)change NS_REQUIRES_SUPER;
/** 当scrollView的contentSize发生改变的时候调用 */
- (void)scrollViewContentSizeDidChange:(nullable NSDictionary *)change NS_REQUIRES_SUPER;
/** 当scrollView的拖拽状态发生改变的时候调用 */
- (void)scrollViewPanStateDidChange:(nullable NSDictionary *)change NS_REQUIRES_SUPER;
/** 多语言配置 language 发生变化时调用
`MJRefreshConfig.defaultConfig.language` .
`placeSubviews` , placeSubviews , . , , super , , .
*/
- (void)i18nDidChange NS_REQUIRES_SUPER;
#pragma mark - 其他
/** 拉拽的百分比(交给子类重写) */
@property (assign, nonatomic) CGFloat pullingPercent;
/** 根据拖拽比例自动切换透明度 */
@property (assign, nonatomic, getter=isAutoChangeAlpha) BOOL autoChangeAlpha MJRefreshDeprecated("请使用automaticallyChangeAlpha属性");
/** 根据拖拽比例自动切换透明度 */
@property (assign, nonatomic, getter=isAutomaticallyChangeAlpha) BOOL automaticallyChangeAlpha;
@end
@interface UILabel(MJRefresh)
+ (instancetype)mj_label;
- (CGFloat)mj_textWidth;
@end
@interface MJRefreshComponent (ChainingGrammar)
#pragma mark - <<< 为 Swift 扩展链式语法 >>> -
/// 自动变化透明度
- (instancetype)autoChangeTransparency:(BOOL)isAutoChange;
/// 刷新开始后立即调用的回调
- (instancetype)afterBeginningAction:(MJRefreshComponentAction)action;
/// 刷新动画开始后立即调用的回调
- (instancetype)endingAnimationBeginningAction:(MJRefreshComponentAction)action;
/// 刷新结束后立即调用的回调
- (instancetype)afterEndingAction:(MJRefreshComponentAction)action;
/// 需要子类必须实现
/// @param scrollView 赋值给的 ScrollView 的 Header/Footer/Trailer
- (instancetype)linkTo:(UIScrollView *)scrollView;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,323 @@
// : https://github.com/CoderMJLee/MJRefresh
// MJRefreshComponent.m
// MJRefresh
//
// Created by MJ Lee on 15/3/4.
// Copyright (c) 2015 . All rights reserved.
//
#import "MJRefreshComponent.h"
#import "MJRefreshConst.h"
#import "MJRefreshConfig.h"
#import "UIView+MJExtension.h"
#import "UIScrollView+MJExtension.h"
#import "UIScrollView+MJRefresh.h"
#import "NSBundle+MJRefresh.h"
@interface MJRefreshComponent()
@property (strong, nonatomic) UIPanGestureRecognizer *pan;
@end
@implementation MJRefreshComponent
#pragma mark -
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
//
[self prepare];
//
self.state = MJRefreshStateIdle;
self.fastAnimationDuration = 0.25;
self.slowAnimationDuration = 0.4;
}
return self;
}
- (void)prepare
{
//
self.autoresizingMask = UIViewAutoresizingFlexibleWidth;
self.backgroundColor = [UIColor clearColor];
}
- (void)layoutSubviews
{
[self placeSubviews];
[super layoutSubviews];
}
- (void)placeSubviews{}
- (void)willMoveToSuperview:(UIView *)newSuperview
{
[super willMoveToSuperview:newSuperview];
// UIScrollView
if (newSuperview && ![newSuperview isKindOfClass:[UIScrollView class]]) return;
//
[self removeObservers];
if (newSuperview) { //
// UIScrollView
_scrollView = (UIScrollView *)newSuperview;
//
self.mj_w = _scrollView.mj_w;
//
self.mj_x = -_scrollView.mj_insetL;
//
_scrollView.alwaysBounceVertical = YES;
// UIScrollViewcontentInset
_scrollViewOriginalInset = _scrollView.mj_inset;
//
[self addObservers];
}
}
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
if (self.state == MJRefreshStateWillRefresh) {
// viewbeginRefreshing
self.state = MJRefreshStateRefreshing;
}
}
#pragma mark - KVO
- (void)addObservers
{
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.scrollView addObserver:self forKeyPath:MJRefreshKeyPathContentOffset options:options context:nil];
[self.scrollView addObserver:self forKeyPath:MJRefreshKeyPathContentSize options:options context:nil];
self.pan = self.scrollView.panGestureRecognizer;
[self.pan addObserver:self forKeyPath:MJRefreshKeyPathPanState options:options context:nil];
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(i18nDidChange) name:MJRefreshDidChangeLanguageNotification object:MJRefreshConfig.defaultConfig];
}
- (void)removeObservers
{
[self.superview removeObserver:self forKeyPath:MJRefreshKeyPathContentOffset];
[self.superview removeObserver:self forKeyPath:MJRefreshKeyPathContentSize];
[self.pan removeObserver:self forKeyPath:MJRefreshKeyPathPanState];
self.pan = nil;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
//
if (!self.userInteractionEnabled) return;
//
if ([keyPath isEqualToString:MJRefreshKeyPathContentSize]) {
[self scrollViewContentSizeDidChange:change];
}
//
if (self.hidden) return;
if ([keyPath isEqualToString:MJRefreshKeyPathContentOffset]) {
[self scrollViewContentOffsetDidChange:change];
} else if ([keyPath isEqualToString:MJRefreshKeyPathPanState]) {
[self scrollViewPanStateDidChange:change];
}
}
- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change{}
- (void)scrollViewContentSizeDidChange:(NSDictionary *)change{}
- (void)scrollViewPanStateDidChange:(NSDictionary *)change{}
- (void)i18nDidChange {
[self placeSubviews];
}
#pragma mark -
#pragma mark
- (void)setRefreshingTarget:(id)target refreshingAction:(SEL)action
{
self.refreshingTarget = target;
self.refreshingAction = action;
}
- (void)setState:(MJRefreshState)state
{
_state = state;
// setState:
MJRefreshDispatchAsyncOnMainQueue([self setNeedsLayout];)
}
#pragma mark
- (void)beginRefreshing
{
[UIView animateWithDuration:self.fastAnimationDuration animations:^{
self.alpha = 1.0;
}];
self.pullingPercent = 1.0;
//
if (self.window) {
self.state = MJRefreshStateRefreshing;
} else {
// 使header inset
if (self.state != MJRefreshStateRefreshing) {
self.state = MJRefreshStateWillRefresh;
// ()
[self setNeedsDisplay];
}
}
}
- (void)beginRefreshingWithCompletionBlock:(void (^)(void))completionBlock
{
self.beginRefreshingCompletionBlock = completionBlock;
[self beginRefreshing];
}
#pragma mark
- (void)endRefreshing
{
MJRefreshDispatchAsyncOnMainQueue(self.state = MJRefreshStateIdle;)
}
- (void)endRefreshingWithCompletionBlock:(void (^)(void))completionBlock
{
self.endRefreshingCompletionBlock = completionBlock;
[self endRefreshing];
}
#pragma mark
- (BOOL)isRefreshing
{
return self.state == MJRefreshStateRefreshing || self.state == MJRefreshStateWillRefresh;
}
#pragma mark
- (void)setAutoChangeAlpha:(BOOL)autoChangeAlpha
{
self.automaticallyChangeAlpha = autoChangeAlpha;
}
- (BOOL)isAutoChangeAlpha
{
return self.isAutomaticallyChangeAlpha;
}
- (void)setAutomaticallyChangeAlpha:(BOOL)automaticallyChangeAlpha
{
_automaticallyChangeAlpha = automaticallyChangeAlpha;
if (self.isRefreshing) return;
if (automaticallyChangeAlpha) {
self.alpha = self.pullingPercent;
} else {
self.alpha = 1.0;
}
}
#pragma mark
- (void)setPullingPercent:(CGFloat)pullingPercent
{
_pullingPercent = pullingPercent;
if (self.isRefreshing) return;
if (self.isAutomaticallyChangeAlpha) {
self.alpha = pullingPercent;
}
}
#pragma mark -
- (void)executeRefreshingCallback
{
if (self.refreshingBlock) {
self.refreshingBlock();
}
if ([self.refreshingTarget respondsToSelector:self.refreshingAction]) {
MJRefreshMsgSend(MJRefreshMsgTarget(self.refreshingTarget), self.refreshingAction, self);
}
if (self.beginRefreshingCompletionBlock) {
self.beginRefreshingCompletionBlock();
}
}
#pragma mark -
- (instancetype)setAnimationDisabled {
self.fastAnimationDuration = 0;
self.slowAnimationDuration = 0;
return self;
}
#pragma mark - <<< Deprecation compatible function >>> -
- (void)setEndRefreshingAnimateCompletionBlock:(MJRefreshComponentEndRefreshingCompletionBlock)endRefreshingAnimateCompletionBlock {
_endRefreshingAnimationBeginAction = endRefreshingAnimateCompletionBlock;
}
@end
@implementation UILabel(MJRefresh)
+ (instancetype)mj_label
{
UILabel *label = [[self alloc] init];
label.font = MJRefreshLabelFont;
label.textColor = MJRefreshLabelTextColor;
label.autoresizingMask = UIViewAutoresizingFlexibleWidth;
label.textAlignment = NSTextAlignmentCenter;
label.backgroundColor = [UIColor clearColor];
return label;
}
- (CGFloat)mj_textWidth {
CGFloat stringWidth = 0;
CGSize size = CGSizeMake(MAXFLOAT, MAXFLOAT);
if (self.attributedText) {
if (self.attributedText.length == 0) { return 0; }
stringWidth = [self.attributedText boundingRectWithSize:size
options:NSStringDrawingUsesLineFragmentOrigin
context:nil].size.width;
} else {
if (self.text.length == 0) { return 0; }
NSAssert(self.font != nil, @"请检查 mj_label's `font` 是否设置正确");
stringWidth = [self.text boundingRectWithSize:size
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName:self.font}
context:nil].size.width;
}
return stringWidth;
}
@end
#pragma mark - <<< Swift >>> -
@implementation MJRefreshComponent (ChainingGrammar)
- (instancetype)autoChangeTransparency:(BOOL)isAutoChange {
self.automaticallyChangeAlpha = isAutoChange;
return self;
}
- (instancetype)afterBeginningAction:(MJRefreshComponentAction)action {
self.beginRefreshingCompletionBlock = action;
return self;
}
- (instancetype)endingAnimationBeginningAction:(MJRefreshComponentAction)action {
self.endRefreshingAnimationBeginAction = action;
return self;
}
- (instancetype)afterEndingAction:(MJRefreshComponentAction)action {
self.endRefreshingCompletionBlock = action;
return self;
}
- (instancetype)linkTo:(UIScrollView *)scrollView {
return self;
}
@end

View File

@ -0,0 +1,37 @@
// 代码地址: https://github.com/CoderMJLee/MJRefresh
// MJRefreshFooter.h
// MJRefresh
//
// Created by MJ Lee on 15/3/5.
// Copyright (c) 2015年 小码哥. All rights reserved.
// 上拉刷新控件
#if __has_include(<MJRefresh/MJRefreshComponent.h>)
#import <MJRefresh/MJRefreshComponent.h>
#else
#import "MJRefreshComponent.h"
#endif
NS_ASSUME_NONNULL_BEGIN
@interface MJRefreshFooter : MJRefreshComponent
/** 创建footer */
+ (instancetype)footerWithRefreshingBlock:(MJRefreshComponentAction)refreshingBlock;
/** 创建footer */
+ (instancetype)footerWithRefreshingTarget:(id)target refreshingAction:(SEL)action;
/** 提示没有更多的数据 */
- (void)endRefreshingWithNoMoreData;
- (void)noticeNoMoreData MJRefreshDeprecated("使用endRefreshingWithNoMoreData");
/** 重置没有更多的数据(消除没有更多数据的状态) */
- (void)resetNoMoreData;
/** 忽略多少scrollView的contentInset的bottom */
@property (assign, nonatomic) CGFloat ignoredScrollViewContentInsetBottom;
/** 自动根据有无数据来显示和隐藏有数据就显示没有数据隐藏。默认是NO */
@property (assign, nonatomic, getter=isAutomaticallyHidden) BOOL automaticallyHidden MJRefreshDeprecated("已废弃此属性开发者请自行控制footer的显示和隐藏");
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,71 @@
// : https://github.com/CoderMJLee/MJRefresh
// MJRefreshFooter.m
// MJRefresh
//
// Created by MJ Lee on 15/3/5.
// Copyright (c) 2015 . All rights reserved.
//
#import "MJRefreshFooter.h"
#import "UIScrollView+MJRefresh.h"
#import "UIView+MJExtension.h"
@interface MJRefreshFooter()
@end
@implementation MJRefreshFooter
#pragma mark -
+ (instancetype)footerWithRefreshingBlock:(MJRefreshComponentAction)refreshingBlock
{
MJRefreshFooter *cmp = [[self alloc] init];
cmp.refreshingBlock = refreshingBlock;
return cmp;
}
+ (instancetype)footerWithRefreshingTarget:(id)target refreshingAction:(SEL)action
{
MJRefreshFooter *cmp = [[self alloc] init];
[cmp setRefreshingTarget:target refreshingAction:action];
return cmp;
}
#pragma mark -
- (void)prepare
{
[super prepare];
//
self.mj_h = MJRefreshFooterHeight;
//
// self.automaticallyHidden = NO;
}
#pragma mark . .
- (instancetype)linkTo:(UIScrollView *)scrollView {
scrollView.mj_footer = self;
return self;
}
#pragma mark -
- (void)endRefreshingWithNoMoreData
{
MJRefreshDispatchAsyncOnMainQueue(self.state = MJRefreshStateNoMoreData;)
}
- (void)noticeNoMoreData
{
[self endRefreshingWithNoMoreData];
}
- (void)resetNoMoreData
{
MJRefreshDispatchAsyncOnMainQueue(self.state = MJRefreshStateIdle;)
}
- (void)setAutomaticallyHidden:(BOOL)automaticallyHidden
{
_automaticallyHidden = automaticallyHidden;
}
@end

Some files were not shown because too many files have changed in this diff Show More