对搜索的完善
@ -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 */,
|
||||
|
||||
@ -49,13 +49,6 @@
|
||||
ReferencedContainer = "container:MusicPlayer.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
<AdditionalOption
|
||||
key = "NSZombieEnabled"
|
||||
value = "YES"
|
||||
isEnabled = "YES">
|
||||
</AdditionalOption>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
||||
22
MusicPlayer/Assets.xcassets/Positive/Home/Artist_Collection'bg.imageset/Contents.json
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
BIN
MusicPlayer/Assets.xcassets/Positive/Home/Artist_Collection'bg.imageset/Ellipse_2220@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 731 B |
BIN
MusicPlayer/Assets.xcassets/Positive/Home/Artist_Collection'bg.imageset/Ellipse_2220@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
22
MusicPlayer/Assets.xcassets/Positive/Home/Artist_Collection'logo.imageset/Contents.json
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
BIN
MusicPlayer/Assets.xcassets/Positive/Home/Artist_Collection'logo.imageset/Group_1597880623@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 715 B |
BIN
MusicPlayer/Assets.xcassets/Positive/Home/Artist_Collection'logo.imageset/Group_1597880623@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
22
MusicPlayer/Assets.xcassets/Positive/Home/Artist_Collectioned'bg.imageset/Contents.json
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
BIN
MusicPlayer/Assets.xcassets/Positive/Home/Artist_Collectioned'bg.imageset/Ellipse_2220@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 435 B |
BIN
MusicPlayer/Assets.xcassets/Positive/Home/Artist_Collectioned'bg.imageset/Ellipse_2220@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 655 B |
22
MusicPlayer/Assets.xcassets/Positive/Home/Artist_Collectioned'logo.imageset/Contents.json
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
BIN
MusicPlayer/Assets.xcassets/Positive/Home/Artist_Collectioned'logo.imageset/Group_1597880623@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 487 B |
BIN
MusicPlayer/Assets.xcassets/Positive/Home/Artist_Collectioned'logo.imageset/Group_1597880623@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 841 B |
|
Before Width: | Height: | Size: 273 B After Width: | Height: | Size: 273 B |
|
Before Width: | Height: | Size: 362 B After Width: | Height: | Size: 362 B |
23
MusicPlayer/Assets.xcassets/Positive/Player/download.imageset/Contents.json
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
BIN
MusicPlayer/Assets.xcassets/Positive/Player/download.imageset/Vector.png
vendored
Normal file
|
After Width: | Height: | Size: 256 B |
BIN
MusicPlayer/Assets.xcassets/Positive/Player/download.imageset/Vector@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 272 B |
BIN
MusicPlayer/Assets.xcassets/Positive/Player/download.imageset/Vector@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 380 B |
@ -71,8 +71,6 @@ extension NotificationCenter{
|
||||
case js_edit_completion
|
||||
///预览数据已更新
|
||||
case positive_browses_reload
|
||||
///列表数据已更新
|
||||
case positive_list_reload
|
||||
///播放器状态变化
|
||||
case switch_player_status
|
||||
///弹出底部音乐模块
|
||||
|
||||
@ -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
|
||||
///全局占位图
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
47
MusicPlayer/MP/Common/Tool(工具封装)/MP_DownloadManager.swift
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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来准确控制播放
|
||||
//为这个currentVideo的resourcePlayerItem创建KVO,分别监听这个item的status,playbackLikelyToKeepUp
|
||||
loadPlayer.currentVideo.resourcePlayerItem.addObserver(self, forKeyPath: "status", options: .new, context: nil)
|
||||
loadPlayer.currentVideo.resourcePlayerItem.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: .new, context: nil)
|
||||
}
|
||||
//启动除了当前播放Video以外的Item的预加载内容
|
||||
// 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 {
|
||||
//当statuVlaue值等于playerItem准备播放的值,说明已经准备好播放
|
||||
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 {
|
||||
//当statuVlaue值等于playerItem准备播放的值,说明已经准备好播放
|
||||
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 {
|
||||
//开始播放
|
||||
|
||||
@ -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]?
|
||||
// ///单曲/视频ID(playlistItemData存在说明这条数据单曲/视频)
|
||||
// let playlistItemData:PlaylistItemData?
|
||||
// ///专辑/歌单ID(navigationEndpoint存在说明这条数据是专辑/歌单)
|
||||
// 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)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -70,15 +70,12 @@ struct JsonBrowses: Codable {
|
||||
struct TabRenderer : Codable {
|
||||
///内容总汇(包含content以及第一阶段ctoke吗)
|
||||
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?
|
||||
///指向标题歌手专辑(通常有3位,0位是标题,1位是歌手,3位是专辑/列表)
|
||||
///指向标题歌手专辑(通常有多位,0位是标题,后续都为副标题)
|
||||
let flexColumns:[FlexColumn]?
|
||||
///指向列表ID和视频ID
|
||||
let overlay:Overlay?
|
||||
///菜单操作(某些情况下flexColumns无法拿到playListId)
|
||||
let menu:Menu?
|
||||
///单曲/视频ID(playlistItemData存在说明这条数据单曲/视频)
|
||||
let playlistItemData:PlaylistItemData?
|
||||
///专辑/歌单ID(navigationEndpoint存在说明这条数据是专辑/歌单)
|
||||
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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//含有列表ID和视频ID
|
||||
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?
|
||||
///指向列表ID和视频ID
|
||||
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)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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?
|
||||
///菜单操作(某些情况下flexColumns无法拿到playListId)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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?
|
||||
}
|
||||
@ -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{
|
||||
///单曲/视频
|
||||
|
||||
@ -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?
|
||||
//}
|
||||
|
||||
@ -10,8 +10,10 @@ import UIKit
|
||||
class MPPositive_SongItemModel: NSObject {
|
||||
///序列号(在当前列表中的排序)
|
||||
var index:Int!
|
||||
///资源路径(等级制,默认取第一条最低质量)
|
||||
///视频源路径(等级制,默认取第一条最低质量)
|
||||
var resourceUrls:[String]?
|
||||
///音频资源路径(等级制,默认取第一条最低质量)
|
||||
var audioUrls:[String]?
|
||||
///封面路径(默认拿最后一条最清晰)
|
||||
var coverUrls:[String]?
|
||||
///预览图片(默认拿最后一条最清晰)
|
||||
|
||||
@ -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() {
|
||||
//检索是否存在moreActionId和moreActionParams
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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] = []
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
@ -12,4 +12,6 @@ class MPPositive_ListAlbumListViewModel: NSObject {
|
||||
var header:MPPositive_ListHeaderViewModel!
|
||||
///列表数据组(由预览模块组成)
|
||||
var items:[MPPositive_BrowseItemViewModel] = []
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -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] = []
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
//补全完成,转化为ViewModel,并添加进listViewVideos
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
///移除选中的song,并更新listViewVideos,移除相同index的值
|
||||
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)
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
@ -7,7 +7,7 @@
|
||||
|
||||
import UIKit
|
||||
///搜索结果数据管理模型
|
||||
class MPPositive_LoadSearchResultsViewModel: NSObject {
|
||||
class MPPositive_SearchResultsLoadViewModel: NSObject {
|
||||
///模块组
|
||||
var sectionLists:[MPPositive_SearchResultListViewModel]!{
|
||||
didSet{
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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)
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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])
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -8,6 +8,9 @@
|
||||
import UIKit
|
||||
//B面播放器封面View(封面,标题,副标题,收藏,下载,进度条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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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:)的时候,animated参数为true,会有阴影和thumb不同步的问题,目前还不知到怎么解决)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
3
Podfile
@ -21,4 +21,7 @@ pod "Kingfisher"
|
||||
#分页工具
|
||||
pod 'JXSegmentedView'
|
||||
pod 'JXPagingView/Paging'
|
||||
#刷新支持
|
||||
pod 'MJRefresh'
|
||||
|
||||
end
|
||||
|
||||
20
Podfile.lock
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -168,7 +168,7 @@ internal extension IQKeyboardManager {
|
||||
textFieldView.isAlertViewTextField() == false {
|
||||
|
||||
// keyboard is already showing. adjust position.
|
||||
optimizedAdjustPosition()
|
||||
self.adjustPosition()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
38
Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/IQTextView/IQPlaceholderable.swift
generated
Normal 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 { }
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -109,8 +109,4 @@ import UIKit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
target = nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,8 +135,4 @@ import UIKit
|
||||
@objc required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
}
|
||||
|
||||
deinit {
|
||||
customView = nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -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!
|
||||
}
|
||||
|
||||
@ -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
@ -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.
|
||||
34
Pods/MJRefresh/MJRefresh/Base/MJRefreshAutoFooter.h
generated
Normal 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
|
||||
216
Pods/MJRefresh/MJRefresh/Base/MJRefreshAutoFooter.m
generated
Normal 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_contentH替换掉self.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
|
||||
21
Pods/MJRefresh/MJRefresh/Base/MJRefreshBackFooter.h
generated
Normal 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
|
||||
158
Pods/MJRefresh/MJRefresh/Base/MJRefreshBackFooter.m
generated
Normal 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
|
||||
151
Pods/MJRefresh/MJRefresh/Base/MJRefreshComponent.h
generated
Normal 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
|
||||
323
Pods/MJRefresh/MJRefresh/Base/MJRefreshComponent.m
generated
Normal 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;
|
||||
// 记录UIScrollView最开始的contentInset
|
||||
_scrollViewOriginalInset = _scrollView.mj_inset;
|
||||
|
||||
// 添加监听
|
||||
[self addObservers];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect
|
||||
{
|
||||
[super drawRect:rect];
|
||||
|
||||
if (self.state == MJRefreshStateWillRefresh) {
|
||||
// 预防view还没显示出来就调用了beginRefreshing
|
||||
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
|
||||
37
Pods/MJRefresh/MJRefresh/Base/MJRefreshFooter.h
generated
Normal 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
|
||||
71
Pods/MJRefresh/MJRefresh/Base/MJRefreshFooter.m
generated
Normal 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
|
||||