对搜索结果展示页的完善

This commit is contained in:
Mr.zhou 2024-05-14 15:04:59 +08:00
parent c9b7e2d935
commit 971cad0358
115 changed files with 10364 additions and 1390 deletions

View File

@ -131,10 +131,12 @@
CBD3135F2BD642D90015D227 /* MPPositive_HomeListFourthCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD3135E2BD642D90015D227 /* MPPositive_HomeListFourthCollectionViewCell.swift */; };
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 */; };
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 */; };
CBDD516F2BECBA6E000F12C5 /* MPPositive_PlayerLoadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBDD516E2BECBA6E000F12C5 /* MPPositive_PlayerLoadViewModel.swift */; };
CBE16B952BF251FF005B7EE6 /* MPPositive_SearchSuggestionItemListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBE16B942BF251FF005B7EE6 /* MPPositive_SearchSuggestionItemListModel.swift */; };
CBE1CB442BDDEAAD00701D57 /* MPPositive_MoreContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBE1CB432BDDEAAD00701D57 /* MPPositive_MoreContentViewController.swift */; };
CBE1CB4A2BDDEBF000701D57 /* MPPositive_MoreListContentCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBE1CB492BDDEBF000701D57 /* MPPositive_MoreListContentCollectionViewCell.swift */; };
CBE1CB4C2BDE440E00701D57 /* MPPositive_ListHeaderModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBE1CB4B2BDE440E00701D57 /* MPPositive_ListHeaderModel.swift */; };
@ -153,10 +155,14 @@
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 */; };
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 */; };
CBF456ED2BF2253D00ABF761 /* MPPositive_SearchResultsShowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBF456EC2BF2253D00ABF761 /* MPPositive_SearchResultsShowView.swift */; };
CBFECE352BF0847F00E07DC4 /* MPPositive_SearchSuggestionItemTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBFECE342BF0847F00E07DC4 /* MPPositive_SearchSuggestionItemTableViewCell.swift */; };
CBFECE372BF0C11000E07DC4 /* MPPositive_JsonSearchSuggestions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBFECE362BF0C11000E07DC4 /* MPPositive_JsonSearchSuggestions.swift */; };
CBFECE392BF0CFFA00E07DC4 /* MPPositive_SearchSuggestionItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBFECE382BF0CFF900E07DC4 /* MPPositive_SearchSuggestionItemModel.swift */; };
CBFECE3B2BF0E51800E07DC4 /* MPPositive_SearchSuggestionListTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBFECE3A2BF0E51800E07DC4 /* MPPositive_SearchSuggestionListTableViewCell.swift */; };
CBFECE3D2BF112D800E07DC4 /* MPPositive_SearchResultShowViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBFECE3C2BF112D800E07DC4 /* MPPositive_SearchResultShowViewController.swift */; };
CBFECE3F2BF1176B00E07DC4 /* MPPositive_JsonSearchResults.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBFECE3E2BF1176B00E07DC4 /* MPPositive_JsonSearchResults.swift */; };
/* End PBXBuildFile section */
@ -289,10 +295,12 @@
CBD3135E2BD642D90015D227 /* MPPositive_HomeListFourthCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_HomeListFourthCollectionViewCell.swift; sourceTree = "<group>"; };
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>"; };
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>"; };
CBDD516E2BECBA6E000F12C5 /* MPPositive_PlayerLoadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_PlayerLoadViewModel.swift; sourceTree = "<group>"; };
CBE16B942BF251FF005B7EE6 /* MPPositive_SearchSuggestionItemListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_SearchSuggestionItemListModel.swift; sourceTree = "<group>"; };
CBE1CB432BDDEAAD00701D57 /* MPPositive_MoreContentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_MoreContentViewController.swift; sourceTree = "<group>"; };
CBE1CB492BDDEBF000701D57 /* MPPositive_MoreListContentCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_MoreListContentCollectionViewCell.swift; sourceTree = "<group>"; };
CBE1CB4B2BDE440E00701D57 /* MPPositive_ListHeaderModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_ListHeaderModel.swift; sourceTree = "<group>"; };
@ -311,10 +319,14 @@
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>"; };
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>"; };
CBF456EC2BF2253D00ABF761 /* MPPositive_SearchResultsShowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_SearchResultsShowView.swift; sourceTree = "<group>"; };
CBFECE342BF0847F00E07DC4 /* MPPositive_SearchSuggestionItemTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_SearchSuggestionItemTableViewCell.swift; sourceTree = "<group>"; };
CBFECE362BF0C11000E07DC4 /* MPPositive_JsonSearchSuggestions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_JsonSearchSuggestions.swift; sourceTree = "<group>"; };
CBFECE382BF0CFF900E07DC4 /* MPPositive_SearchSuggestionItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_SearchSuggestionItemModel.swift; sourceTree = "<group>"; };
CBFECE3A2BF0E51800E07DC4 /* MPPositive_SearchSuggestionListTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_SearchSuggestionListTableViewCell.swift; sourceTree = "<group>"; };
CBFECE3C2BF112D800E07DC4 /* MPPositive_SearchResultShowViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_SearchResultShowViewController.swift; sourceTree = "<group>"; };
CBFECE3E2BF1176B00E07DC4 /* MPPositive_JsonSearchResults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_JsonSearchResults.swift; sourceTree = "<group>"; };
E2C6C85BFD4CD80DBA96D149 /* Pods-MusicPlayer.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MusicPlayer.release.xcconfig"; path = "Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer.release.xcconfig"; sourceTree = "<group>"; };
@ -678,6 +690,7 @@
CBE1CB4D2BDE4BD800701D57 /* MPPositive_ListAlbumListViewModel.swift */,
CBE1CB4F2BDE4CC500701D57 /* MPPositive_ListHeaderViewModel.swift */,
CBEE8E372BEB92CC007DA798 /* MPPositive_SongViewModel.swift */,
CBE16B942BF251FF005B7EE6 /* MPPositive_SearchSuggestionItemListModel.swift */,
CBF456DC2BF1E72F00ABF761 /* MPPositive_SearchResultListViewModel.swift */,
CBF456E02BF1EB4300ABF761 /* MPPositive_SearchResultItemViewModel.swift */,
);
@ -689,6 +702,7 @@
children = (
CBD0CC582BDA238100C4B64D /* MPPositive_BrowseLoadViewModel.swift */,
CBDD516E2BECBA6E000F12C5 /* MPPositive_PlayerLoadViewModel.swift */,
CBF456E22BF2086600ABF761 /* MPPositive_LoadSearchResultsViewModel.swift */,
);
path = LoadViewModels;
sourceTree = "<group>";
@ -794,8 +808,12 @@
CBCB50212BD118BB009760B3 /* Search */ = {
isa = PBXGroup;
children = (
CBF456EA2BF222EC00ABF761 /* MPPositive_SearchSuggestionsView.swift */,
CBFECE342BF0847F00E07DC4 /* MPPositive_SearchSuggestionItemTableViewCell.swift */,
CBFECE3A2BF0E51800E07DC4 /* MPPositive_SearchSuggestionListTableViewCell.swift */,
CBF456EC2BF2253D00ABF761 /* MPPositive_SearchResultsShowView.swift */,
CBF456E82BF21E0E00ABF761 /* MPPositive_SearchResultPreviewShowView.swift */,
CBD5E80B2BF33D0200A3EBED /* MPPositive_SearchResultTypeShowView.swift */,
CBF456E62BF20BBD00ABF761 /* MPPositive_SearchResultShowTableViewCell.swift */,
);
path = Search;
sourceTree = "<group>";
@ -1078,10 +1096,12 @@
CBCAFB5A2BB3C2A000BC6520 /* LayoutConstraint.swift in Sources */,
CBCB4FEF2BD11402009760B3 /* MPSideA_NavigationController.swift in Sources */,
CBF456DD2BF1E72F00ABF761 /* MPPositive_SearchResultListViewModel.swift in Sources */,
CBD5E80C2BF33D0200A3EBED /* MPPositive_SearchResultTypeShowView.swift in Sources */,
CBCB35212BD7ACE900802900 /* MPPositive_JsonBrowse.swift in Sources */,
CBCB4FF62BD11402009760B3 /* MPSideA_DeleteViewController.swift in Sources */,
CBFECE392BF0CFFA00E07DC4 /* MPPositive_SearchSuggestionItemModel.swift in Sources */,
CBF456DF2BF1E8A500ABF761 /* MPPositive_SearchResultItemModel.swift in Sources */,
CBF456E72BF20BBD00ABF761 /* MPPositive_SearchResultShowTableViewCell.swift in Sources */,
CBDD516F2BECBA6E000F12C5 /* MPPositive_PlayerLoadViewModel.swift in Sources */,
CBEE8E382BEB92CC007DA798 /* MPPositive_SongViewModel.swift in Sources */,
CBCB50122BD11402009760B3 /* MPSideA_Home_FirstListCollectionViewCell.swift in Sources */,
@ -1100,6 +1120,7 @@
CBCAFB662BB3C82C00BC6520 /* MP_LunchViewController.swift in Sources */,
CBCB4FEA2BD11402009760B3 /* MPSideA_MusicModel.swift in Sources */,
CBBFA91A2BBA846600057FD5 /* CoreDataDelegete.swift in Sources */,
CBF456E32BF2086600ABF761 /* MPPositive_LoadSearchResultsViewModel.swift in Sources */,
CB56612D2BE0DF8C00CFD014 /* MP_WebWork.swift in Sources */,
CBFECE352BF0847F00E07DC4 /* MPPositive_SearchSuggestionItemTableViewCell.swift in Sources */,
CBCB50042BD11402009760B3 /* MPSideA_HomeViewController.swift in Sources */,
@ -1118,6 +1139,7 @@
CBCB50082BD11402009760B3 /* MPSideA_BottomShowView.swift in Sources */,
CBEE8E342BEB16BB007DA798 /* MPPositive_PlayerSilder.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 */,
CBCB500E2BD11402009760B3 /* MPSideA_CenterTableViewCell.swift in Sources */,
@ -1128,11 +1150,13 @@
CB0918A32BD26B2F006D2B39 /* MPPositive_LibraryViewController.swift in Sources */,
CBEE8E322BEB0FC0007DA798 /* MPPositive_PlayerCoverView.swift in Sources */,
CBC32A552BD8DFB900687171 /* MPPositive_BrowseModuleListViewModel.swift in Sources */,
CBF456E92BF21E0E00ABF761 /* MPPositive_SearchResultPreviewShowView.swift in Sources */,
CBF456E12BF1EB4300ABF761 /* MPPositive_SearchResultItemViewModel.swift in Sources */,
CBE1CB4E2BDE4BD800701D57 /* MPPositive_ListAlbumListViewModel.swift in Sources */,
CBD313572BD63B390015D227 /* MPPositive_HomeListSecondCollectionViewCell.swift in Sources */,
0096622D2BB14A5A00FCA65F /* AppDelegate.swift in Sources */,
CBC32A532BD8D9F300687171 /* MPPositive_BrowseItemModel.swift in Sources */,
CBE16B952BF251FF005B7EE6 /* MPPositive_SearchSuggestionItemListModel.swift in Sources */,
CBCB4FEC2BD11402009760B3 /* MPSideA_AddViewController.swift in Sources */,
CBB75B0B2BEF0BC400B3FF9A /* MPPositive_DownloadItemModel.swift in Sources */,
CBCB50172BD11402009760B3 /* MPSideA_Home_RowListsTableViewCell.swift in Sources */,
@ -1141,7 +1165,6 @@
CBD313612BD6453A0015D227 /* MPPositive_HomeListFifthCollectionViewCell.swift in Sources */,
CBE1CB4A2BDDEBF000701D57 /* MPPositive_MoreListContentCollectionViewCell.swift in Sources */,
CBCB4FF82BD11402009760B3 /* MPSideA_MoreViewController.swift in Sources */,
CBFECE3B2BF0E51800E07DC4 /* MPPositive_SearchSuggestionListTableViewCell.swift in Sources */,
CBCB50142BD11402009760B3 /* MPSideA_Home_FourthListCollectionViewCell.swift in Sources */,
CB5661292BE09D0500CFD014 /* MPPositive_JsonPlayer.swift in Sources */,
CBCB50062BD11402009760B3 /* MPSideA_PlayerViewController.swift in Sources */,
@ -1171,6 +1194,7 @@
CBE477B12BB16CCC0031C14B /* Macro.swift in Sources */,
CBCB4FFE2BD11402009760B3 /* MPSideA_ServiceViewController.swift in Sources */,
CBE2C4CB2BC7BE5D00F283A7 /* MP_NetWorkManager.swift in Sources */,
CBF456EB2BF222EC00ABF761 /* MPPositive_SearchSuggestionsView.swift in Sources */,
CBBFA91E2BBA9B5C00057FD5 /* Notification.swift in Sources */,
CBCB321A2BD7578500802900 /* MP_LocationManager.swift in Sources */,
CBCB4FEB2BD11402009760B3 /* MPSideA_MusicViewModel.swift in Sources */,

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 608 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 929 B

View File

@ -33,12 +33,26 @@ class MP_LunchViewController: UIViewController {
MP_LocationManager.shared.setLocationPermission(self, complete: nil)
//youtube
MP_WebWork.shared.pingYoutubeHome()
NotificationCenter.notificationKey.add(observer: self, selector: #selector(jumpAction(_:)), notificationName: .js_edit_completion)
}
deinit {
//
timer.invalidate()
timer = nil
NotificationCenter.default.removeObserver(self)
}
@objc private func jumpAction(_ sender:Notification) {
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
//
timer.isPaused = true
//
accessAppdelegate.switch_positive()
}
}
//
@objc fileprivate func timerActionClick(_ link:CADisplayLink) {
if maxTimes > currentTimes {
@ -52,15 +66,15 @@ class MP_LunchViewController: UIViewController {
progressView.setProgress(value)
}
}else {
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
//
timer.isPaused = true
//
// accessAppdelegate.switch_aSide()
accessAppdelegate.switch_positive()
}
print("进度已满")
// DispatchQueue.main.async {
// [weak self] in
// guard let self = self else {return}
// //
// timer.isPaused = true
// //
// accessAppdelegate.switch_positive()
// }
}
}
}

View File

@ -67,6 +67,8 @@ extension NotificationCenter{
///A
case sideA_rename_music
//MARK: - b
///JS
case js_edit_completion
///
case positive_browses_reload
///

View File

@ -21,3 +21,10 @@ extension String {
return anyClass
}
}
extension Range where Bound == String.Index {
func toNSRange(in string: String) -> NSRange {
let utf16IndexStart = string.utf16.distance(from: string.utf16.startIndex, to: lowerBound)
let utf16IndexEnd = string.utf16.distance(from: string.utf16.startIndex, to: upperBound)
return NSRange(location: utf16IndexStart, length: utf16IndexEnd - utf16IndexStart)
}
}

View File

@ -7,6 +7,10 @@
import UIKit
import Foundation
import AVFoundation
@_exported import JXSegmentedView
@_exported import JXPagingView
//JXPagingListContainerViewextensionJXSegmentedViewListContainer
extension JXPagingListContainerView: JXSegmentedViewListContainer {}
//MARK: -
///
let screen_Width = UIScreen.main.bounds.width

View File

@ -519,9 +519,9 @@ extension MP_NetWorkManager {
}
}
///
///
/// - Parameter text:
func requestSearchResults(_ text:String) {
func requestSearchPreviewResults(_ text:String, completion:@escaping (([MPPositive_SearchResultListViewModel]) -> Void)) {
//
let path = header+point+search
//url
@ -548,17 +548,75 @@ extension MP_NetWorkManager {
]
]
]
//
requestPostSearchPreviewResults(url, parameters: parameters) { result in
completion(result)
}
//
private func requestPostSearchResults(_ url:URL, parameters:Parameters) {
}
//
private func requestPostSearchPreviewResults(_ url:URL, parameters:Parameters, completion:@escaping (([MPPositive_SearchResultListViewModel]) -> Void)) {
//post
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonSearchResults.self) { [weak self] (response) in
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonSearchPreviewResults.self) { [weak self] (response) in
guard let self = self else {return}
switch response.result {
case .success(let value):
if let contents = value.contents?.tabbedSearchResultsRenderer?.tabs?.first?.tabRenderer?.content?.sectionListRenderer?.contents {
parsingSearchResults(contents)
let result = parsingSearchPreviewResults(contents)
print("一共搜索到\(result.count)相关内容模块")
completion(result)
}
case .failure(let error):
//
print("Request failed: \(error)")
}
}
}
///
/// - Parameters:
/// - query:
/// - params:
func requestSearchTypeResults(_ query:String, params:String, completion:@escaping (([MPPositive_SearchResultItemViewModel], String?, String?))->Void) {
//
let path = header+point+search
//url
guard let url = URL(string: path) else {
print("Url is Incorrect")
return
}
//
let parameters:[String:Any] = [
"query":query,
"prettyPrint":"false",
"params":params,
"context":[
"client":[
//web
"clientName": "WEB_REMIX",
"visitorData":visitorData,
//访
"clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00",
"platform":"DESKTOP",
//
"hl":Language_first_local,
//
"gl":Location_First
]
]
]
requestPostSearchTypeResults(url, parameters: parameters) { result in
completion(result)
}
}
//
private func requestPostSearchTypeResults(_ url:URL, parameters:Parameters, completion:@escaping (([MPPositive_SearchResultItemViewModel], String?, String?))->Void) {
//post
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonSearchTypeResults.self) { [weak self] (response) in
guard let self = self else {return}
switch response.result {
case .success(let value):
if let musicShelfRenderer = value.contents?.tabbedSearchResultsRenderer?.tabs?.first?.tabRenderer?.content?.sectionListRenderer?.contents?.first?.musicShelfRenderer {
completion(parsingSearchTypeResults(musicShelfRenderer))
}
case .failure(let error):
//
@ -869,21 +927,187 @@ extension MP_NetWorkManager {
completion(sections)
}
}
///_SearchResults
private func parsingSearchResults(_ contents:[JsonSearchResults.Contents.TabbedSearchResultsRenderer.Tab.TabRenderer.Content.SectionListRenderer.Content]) {
///_SearchPreviewResults
private func parsingSearchPreviewResults(_ contents:[JsonSearchPreviewResults.Contents.TabbedSearchResultsRenderer.Tab.TabRenderer.Content.SectionListRenderer.Content]) -> [MPPositive_SearchResultListViewModel]{
var resultListSections:[MPPositive_SearchResultListViewModel] = []
contents.forEach { content in
let resultList = MPPositive_SearchResultListViewModel()
//
if let musicCardShelfRenderer = content.musicCardShelfRenderer {
//
if let title = musicCardShelfRenderer.title {
//
resultList.title = title.runs?.reduce("", { $0 + ($1.text ?? "")})
}
resultList.title = musicCardShelfRenderer.header?.musicCardShelfHeaderBasicRenderer?.title?.runs?.reduce("", { $0 + ($1.text ?? "")})
////
let item = MPPositive_SearchResultItemModel()
//
item.reviewUrls = musicCardShelfRenderer.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.map({$0.url ?? ""})
//ID
if let title = musicCardShelfRenderer.title {
title.runs?.forEach({ run in
item.title = run.text
if run.navigationEndpoint?.watchEndpoint != nil {
///
item.videoId = run.navigationEndpoint?.watchEndpoint?.videoId
item.itemType = .single
}else {
//
///
item.browseId = run.navigationEndpoint?.browseEndpoint?.browseId
if run.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType == "MUSIC_PAGE_TYPE_ARTIST" {
//
item.itemType = .artist
}else {
//
item.itemType = .list
}
}
})
}
//
if let subtitle = musicCardShelfRenderer.subtitle {
item.subtitle = subtitle.runs?.reduce("", { $0 + ($1.text ?? "")})
}
//
resultList.previewItemViews.append(.init(item))
//
if let contents = musicCardShelfRenderer.contents {
contents.forEach { content in
//
let item = MPPositive_SearchResultItemModel()
if let musicResponsiveListItemRenderer = content.musicResponsiveListItemRenderer {
//
item.reviewUrls = musicResponsiveListItemRenderer.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.map({$0.url ?? ""})
//
for (index, flexColumn) in (musicResponsiveListItemRenderer.flexColumns ?? []).enumerated() {
if index == 0 {
//
item.title = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")})
}else {
//
item.subtitle = (item.subtitle ?? "") + (flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")}) ?? "")
}
}
//id
if musicResponsiveListItemRenderer.playlistItemData != nil {
//
item.itemType = .single
item.videoId = musicResponsiveListItemRenderer.playlistItemData?.videoId
}else {
///
item.browseId = musicResponsiveListItemRenderer.navigationEndpoint?.browseEndpoint?.browseId
if musicResponsiveListItemRenderer.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType == "MUSIC_PAGE_TYPE_ARTIST" {
//
item.itemType = .artist
}else {
///
item.itemType = .list
}
}
}
if item.title != nil {
resultList.previewItemViews.append(.init(item))
}
}
}
//,queryparams
resultList.query = nil
resultList.params = nil
}else if let musicShelfRenderer = content.musicShelfRenderer {
//,
//
resultList.title = musicShelfRenderer.title?.runs?.reduce("", { $0 + ($1.text ?? "")})
//
musicShelfRenderer.contents?.forEach({ content in
/////
let item = MPPositive_SearchResultItemModel()
//
item.reviewUrls = content.musicResponsiveListItemRenderer?.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.map({$0.url ?? ""})
//
for (index, flexColumn) in (content.musicResponsiveListItemRenderer?.flexColumns ?? []).enumerated() {
if index == 0 {
//
item.title = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")})
}else {
//
item.subtitle = (item.subtitle ?? "") + (flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")}) ?? "")
}
}
//id
if content.musicResponsiveListItemRenderer?.playlistItemData != nil {
//
item.itemType = .single
item.videoId = content.musicResponsiveListItemRenderer?.playlistItemData?.videoId
}else {
///
item.browseId = content.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseId
if content.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType == "MUSIC_PAGE_TYPE_ARTIST" {
//
item.itemType = .artist
}else {
///
item.itemType = .list
}
}
resultList.previewItemViews.append(.init(item))
})
//queryparams
if let searchEndpoint = musicShelfRenderer.bottomEndpoint?.searchEndpoint {
resultList.query = searchEndpoint.query
resultList.params = searchEndpoint.params
}
}
resultListSections.append(resultList)
}
resultListSections = resultListSections.filter({$0.previewItemViews.count != 0})
return resultListSections
}
///
private func parsingSearchTypeResults(_ musicShelfRenderer:JsonSearchTypeResults.Contents.TabbedSearchResultsRenderer.Tab.TabRenderer.Content.SectionListRenderer.Content.MusicShelfRenderer) -> ([MPPositive_SearchResultItemViewModel], String?, String?) {
var array:[MPPositive_SearchResultItemViewModel] = []
if let contents = musicShelfRenderer.contents {
///
contents.forEach({ content in
/////
let item = MPPositive_SearchResultItemModel()
//
item.reviewUrls = content.musicResponsiveListItemRenderer?.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.map({$0.url ?? ""})
//
for (index, flexColumn) in (content.musicResponsiveListItemRenderer?.flexColumns ?? []).enumerated() {
if index == 0 {
//
item.title = flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")})
}else {
//
item.subtitle = (item.subtitle ?? "") + (flexColumn.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.reduce("", { $0 + ($1.text ?? "")}) ?? "")
}
}
//id
if content.musicResponsiveListItemRenderer?.playlistItemData != nil {
//
item.itemType = .single
item.videoId = content.musicResponsiveListItemRenderer?.playlistItemData?.videoId
}else {
///
item.browseId = content.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseId
if content.musicResponsiveListItemRenderer?.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType == "MUSIC_PAGE_TYPE_ARTIST" {
//
item.itemType = .artist
}else {
///
item.itemType = .list
}
}
//
array.append(.init(item))
})
}
var continuation:String?
var clickTrackingParams:String?
if let continuations = musicShelfRenderer.continuations {
///
continuation = continuations.nextContinuationData?.continuation
clickTrackingParams = continuations.nextContinuationData?.clickTrackingParams
}
return (array, continuation, clickTrackingParams)
}
//MARK: -

View File

@ -174,6 +174,8 @@ extension MP_WebWork: WKNavigationDelegate, WKUIDelegate {
print("注入代码失败:\(error)")
}else {
print("注入代码完成")
//
NotificationCenter.notificationKey.post(notificationName: .js_edit_completion)
}
}
}

View File

@ -6,9 +6,9 @@
//
import UIKit
///
struct JsonSearchResults: Codable {
//MARK: -
///
struct JsonSearchPreviewResults: Codable {
let contents:Contents?
enum CodingKeys: String, CodingKey {
case contents = "contents"
@ -76,7 +76,7 @@ struct JsonSearchResults: Codable {
struct Content: Codable {
///
let musicCardShelfRenderer:MusicCardShelfRenderer?
///
///
let musicShelfRenderer:MusicShelfRenderer?
enum CodingKeys: String, CodingKey {
case musicCardShelfRenderer = "musicCardShelfRenderer"
@ -182,7 +182,7 @@ struct JsonSearchResults: Codable {
struct NavigationEndpoint: Codable {
///
let watchEndpoint:WatchEndpoint?
///
/////
let browseEndpoint:BrowseEndpoint?
enum CodingKeys: String, CodingKey {
case watchEndpoint = "watchEndpoint"
@ -205,13 +205,35 @@ struct JsonSearchResults: Codable {
}
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)
}
}
}
}
}
@ -299,7 +321,7 @@ struct JsonSearchResults: Codable {
let flexColumns:[FlexColumn]?
////IDplaylistItemData/
let playlistItemData:PlaylistItemData?
////IDnavigationEndpoint/
////ID/navigationEndpoint//
let navigationEndpoint:NavigationEndpoint?
enum CodingKeys: String, CodingKey {
case thumbnail = "thumbnail"
@ -416,13 +438,35 @@ struct JsonSearchResults: Codable {
}
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)
}
}
}
}
}
@ -435,14 +479,18 @@ struct JsonSearchResults: 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]?
@ -599,13 +647,62 @@ struct JsonSearchResults: Codable {
}
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 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)
}
}
}
@ -618,5 +715,295 @@ struct JsonSearchResults: Codable {
}
}
}
///
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:Continuations?
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(Continuations.self, forKey: .continuations)
}
///
struct Content: Codable {
let musicResponsiveListItemRenderer:MusicResponsiveListItemRenderer?
enum CodingKeys: String, CodingKey {
case musicResponsiveListItemRenderer = "musicResponsiveListItemRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicResponsiveListItemRenderer = try values.decodeIfPresent(MusicResponsiveListItemRenderer.self, forKey: .musicResponsiveListItemRenderer)
}
struct MusicResponsiveListItemRenderer: Codable {
///
let thumbnail:Thumbnail?
///(0)
let flexColumns:[FlexColumn]?
////IDplaylistItemData/
let playlistItemData:PlaylistItemData?
////IDnavigationEndpoint/
let navigationEndpoint:NavigationEndpoint?
enum CodingKeys: String, CodingKey {
case thumbnail = "thumbnail"
case flexColumns = "flexColumns"
case playlistItemData = "playlistItemData"
case navigationEndpoint = "navigationEndpoint"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
thumbnail = try values.decodeIfPresent(Thumbnail.self, forKey: .thumbnail)
flexColumns = try values.decodeIfPresent([FlexColumn].self, forKey: .flexColumns)
playlistItemData = try values.decodeIfPresent(PlaylistItemData.self, forKey: .playlistItemData)
navigationEndpoint = try values.decodeIfPresent(NavigationEndpoint.self, forKey: .navigationEndpoint)
}
struct Thumbnail: Codable {
let musicThumbnailRenderer:MusicThumbnailRenderer?
enum CodingKeys: String, CodingKey {
case musicThumbnailRenderer = "musicThumbnailRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicThumbnailRenderer = try values.decodeIfPresent(MusicThumbnailRenderer.self, forKey: .musicThumbnailRenderer)
}
struct MusicThumbnailRenderer: Codable {
let thumbnail:Thumbnail?
enum CodingKeys: String, CodingKey {
case thumbnail = "thumbnail"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
thumbnail = try values.decodeIfPresent(Thumbnail.self, forKey: .thumbnail)
}
struct Thumbnail: Codable {
let thumbnails:[Thumbnails]?
enum CodingKeys: String, CodingKey {
case thumbnails = "thumbnails"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
thumbnails = try values.decodeIfPresent([Thumbnails].self, forKey: .thumbnails)
}
struct Thumbnails: Codable {
let url:String?
enum CodingKeys: String, CodingKey {
case url = "url"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
url = try values.decodeIfPresent(String.self, forKey: .url)
}
}
}
}
}
struct FlexColumn: Codable {
let musicResponsiveListItemFlexColumnRenderer:MusicResponsiveListItemFlexColumnRenderer?
enum CodingKeys: String, CodingKey {
case musicResponsiveListItemFlexColumnRenderer = "musicResponsiveListItemFlexColumnRenderer"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
musicResponsiveListItemFlexColumnRenderer = try values.decodeIfPresent(MusicResponsiveListItemFlexColumnRenderer.self, forKey: .musicResponsiveListItemFlexColumnRenderer)
}
struct MusicResponsiveListItemFlexColumnRenderer: Codable {
let text:Text?
enum CodingKeys: String, CodingKey {
case text = "text"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
text = try values.decodeIfPresent(Text.self, forKey: .text)
}
struct Text: Codable {
let runs:[Run]?
enum CodingKeys: String, CodingKey {
case runs = "runs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
runs = try values.decodeIfPresent([Run].self, forKey: .runs)
}
struct Run: Codable {
///
let text:String?
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 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 Continuations: Codable {
let nextContinuationData:NextContinuationData?
enum CodingKeys: String, CodingKey {
case nextContinuationData = "nextContinuationData"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
nextContinuationData = try values.decodeIfPresent(NextContinuationData.self, forKey: .nextContinuationData)
}
struct NextContinuationData: Codable {
///
let continuation:String?
///
let clickTrackingParams:String?
enum CodingKeys: String, CodingKey {
case continuation = "continuation"
case clickTrackingParams = "clickTrackingParams"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
continuation = try values.decodeIfPresent(String.self, forKey: .continuation)
clickTrackingParams = try values.decodeIfPresent(String.self, forKey: .clickTrackingParams)
}
}
}
}
}
}
}
}
}
}
}
}

View File

@ -16,7 +16,7 @@ class MPPositive_SearchResultItemModel: NSObject {
var subtitle:String?
////VideoID
var videoId:String?
////browseID
///ID//
var browseId:String?
///
var itemType:BrowseItemType?

View File

@ -6,7 +6,7 @@
//
import UIKit
import Kingfisher
class MPPositive_SearchResultItemViewModel: NSObject {
///
var reviewUrl:URL?
@ -18,5 +18,29 @@ class MPPositive_SearchResultItemViewModel: NSObject {
var item:MPPositive_SearchResultItemModel!
init(_ resultItem:MPPositive_SearchResultItemModel) {
super.init()
item = resultItem
configure()
}
private func configure() {
if let url = URL(string: item.reviewUrls?.last ?? "") {
reviewUrl = url
}
if item.title != nil {
title = item.title
}
if item.subtitle != nil {
subtitle = item.subtitle
}
}
//
func setImage(_ imageView:UIImageView) {
if reviewUrl != nil {
imageView.kf.setImage(with: reviewUrl, placeholder: placeholderImage)
}
if item.itemType == .artist {
imageView.layer.cornerRadius = 30*width
}else {
imageView.layer.cornerRadius = 10*width
}
}
}

View File

@ -10,6 +10,41 @@ import UIKit
class MPPositive_SearchResultListViewModel: NSObject {
///
var title:String!
///
var itemViews:[MPPositive_SearchResultItemViewModel] = []
////3-4
var previewItemViews:[MPPositive_SearchResultItemViewModel] = []
///()
var query:String?
///()
var params:String?
///
var allItemViews:[MPPositive_SearchResultItemViewModel] = []{
didSet{
print("用户加载\(title ?? "")的新内容")
if completionBlock != nil {
completionBlock!()
}
}
}
///
var continuation:String?
///
var itct:String?
///
var completionBlock:(() -> Void)?
///
func requestSearchType() {
guard let query = self.query, let params = self.params else {
print("当前模块已是全部内容")
return
}
//
MP_NetWorkManager.shared.requestSearchTypeResults(query, params: params) { [weak self] (items, continuation, itct) in
guard let self = self else {return}
//
allItemViews.append(contentsOf: items)
//
self.continuation = continuation
self.itct = itct
}
}
}

View File

@ -0,0 +1,32 @@
//
// MPPositive_SearchSuggestionItemListModel.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/13.
//
import UIKit
class MPPositive_SearchSuggestionItemListModel: NSObject {
///
var searchText:String?
///
var attributedTexts:[NSAttributedString]?
init(_ text:String, suggestions:[[MPPositive_SearchSuggestionItemModel]]) {
super.init()
searchText = text
//suggestions
let titles:[String] = suggestions.flatMap({$0.compactMap({($0.title)})})
self.attributedTexts = titles.map({createAttributedString(for: $0)})
}
//
func createAttributedString(for string: String) -> NSAttributedString {
let attributedString = NSMutableAttributedString(string: string, attributes: [.foregroundColor: UIColor.white, .font:UIFont.systemFont(ofSize: 14*width, weight: .medium)])
if let range = string.range(of: searchText ?? "") {
let narange = range.toNSRange(in: string)
attributedString.addAttributes([.foregroundColor:UIColor(hex: "#80F988")], range: narange)
}
return attributedString
}
}

View File

@ -0,0 +1,36 @@
//
// MPPositive_LoadSearchResultsViewModel.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/13.
//
import UIKit
///
class MPPositive_LoadSearchResultsViewModel: NSObject {
///
var sectionLists:[MPPositive_SearchResultListViewModel]!{
didSet{
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
//
if resultReloadBlock != nil {
resultReloadBlock!()
}
}
}
}
var resultReloadBlock:(() -> Void)?
//
init(_ text:String){
super.init()
getSearchResults(text)
}
//
private func getSearchResults(_ text:String) {
MP_NetWorkManager.shared.requestSearchPreviewResults(text) { [weak self] results in
self?.sectionLists = results
}
}
}

View File

@ -45,6 +45,9 @@ class MPPositive_HomeViewController: MPPositive_BaseViewController{
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)
@ -87,7 +90,8 @@ class MPPositive_HomeViewController: MPPositive_BaseViewController{
//MARK: -
//
@objc private func menuRightClick(_ sender:UIButton) {
// let array = MPPositive_DownloadItemModel.fetchAll()
// print(array)
}
}
//MARK: - tableView

View File

@ -82,9 +82,9 @@ class MPPositive_ListShowViewController: MPPositive_BaseViewController {
}
//
private func reload() {
listOrAlbum.header.setUrltoImage(coverImageView)
titleLabel.text = listOrAlbum.header.title
descriptionLabel.text = listOrAlbum.header._description
listOrAlbum.header?.setUrltoImage(coverImageView)
titleLabel.text = listOrAlbum.header?.title
descriptionLabel.text = listOrAlbum.header?._description
playListNumberLabel.text = "Play all (\(listOrAlbum.items.count))"
}
//MARK: - UI

View File

@ -8,11 +8,205 @@
import UIKit
///
class MPPositive_SearchResultShowViewController: MPPositive_BaseViewController {
//MARK: - View
//textField
private lazy var searchTextField:UITextField = {
let textField = UITextField()
textField.delegate = self
textField.font = .systemFont(ofSize: 14*width, weight: .regular)
textField.textColor = .white
//
let attributedText = NSAttributedString(string: "Search songs,artists,playlists", attributes: [.font:UIFont.systemFont(ofSize: 14*width, weight: .regular), .foregroundColor:UIColor(hex: "#666666")])
textField.attributedPlaceholder = attributedText
return textField
}()
//
private lazy var deleteBtn:UIButton = {
let btn:UIButton = .init()
btn.setBackgroundImage(UIImage(named: "Search_Delete'logo"), for: .normal)
btn.addTarget(self, action: #selector(deleteTextClick(_ :)), for: .touchUpInside)
return btn
}()
//cancel
private lazy var cancelBtn:UIButton = {
let btn:UIButton = .init()
btn.setTitle("Cancel", for: .normal)
btn.setTitleColor(.init(hex: "#FFFFFF", alpha: 0.85), for: .normal)
btn.titleLabel?.font = .systemFont(ofSize: 14*width, weight: .regular)
btn.addTarget(self, action: #selector(backPopClick(_ :)), for: .touchUpInside)
return btn
}()
//
private var debounceTimer: Timer?
//
private var suggestionList:MPPositive_SearchSuggestionItemListModel!{
didSet{
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
if suggestionList != nil {
//
suggestionView.isHidden = false
suggestionView.suggestions = suggestionList.attributedTexts
}else {
suggestionView.isHidden = true
suggestionView.suggestions = nil
}
}
}
}
//
private var searchText:String?
//MARK: - View
private lazy var suggestionView:MPPositive_SearchSuggestionsView = .init(frame: .zero)
//MARK: - View
private lazy var resultsShowView:MPPositive_SearchResultsShowView = .init(frame: .zero)
override func viewDidLoad() {
super.viewDidLoad()
setTitle("Result")
setPopBtn()
configure()
suggestionView.selectedTextBlock = {
[weak self] (text) in
guard let self = self else {return}
searchText = text
searchTextField.text = text
resultsShowView.loadModel = .init(text)
suggestionView.isHidden = true
}
resultsShowView.scrollBlock = {
[weak self] in
self?.view?.endEditing(true)
}
}
deinit{
debounceTimer = nil
}
private func configure() {
//
let searchView = createSearchView()
navView.addSubview(searchView)
searchView.snp.makeConstraints { make in
make.width.equalTo(275*width)
make.height.equalTo(40*width)
make.centerY.equalToSuperview()
make.left.equalToSuperview().offset(18*width)
}
navView.addSubview(cancelBtn)
cancelBtn.snp.makeConstraints { make in
make.right.equalToSuperview().offset(-18*width)
make.centerY.equalToSuperview()
}
//suggestionView
view.addSubview(suggestionView)
suggestionView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.top.equalTo(navView.snp.bottom)
}
suggestionView.isHidden = true
//resultsShowView
view.addSubview(resultsShowView)
resultsShowView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.top.equalTo(navView.snp.bottom)
}
resultsShowView.isHidden = true
}
//
private func createSearchView() -> UIView{
let searchView:UIView = UIView()
searchView.backgroundColor = .init(hex: "#000000")
searchView.isUserInteractionEnabled = true
searchView.layer.masksToBounds = true
searchView.layer.cornerRadius = 20*width
//icon
let iconImageView = UIImageView(image: .init(named: "Search_ICON'logo"))
searchView.addSubview(iconImageView)
iconImageView.snp.makeConstraints { make in
make.height.width.equalTo(24*width)
make.left.equalToSuperview().offset(8*width)
make.centerY.equalToSuperview()
}
searchView.addSubview(deleteBtn)
deleteBtn.snp.makeConstraints { make in
make.height.width.equalTo(24*width)
make.right.equalToSuperview().offset(-12*width)
make.centerY.equalToSuperview()
}
//textField
searchView.addSubview(searchTextField)
searchTextField.snp.makeConstraints { make in
make.top.bottom.equalToSuperview()
make.left.equalTo(iconImageView.snp.right).offset(14*width)
make.right.equalTo(deleteBtn.snp.left).offset(-12*width)
}
return searchView
}
//
@objc private func deleteTextClick(_ sender:UIButton) {
//View
searchTextField.text = ""
searchText = ""
suggestionView.suggestions = nil
resultsShowView.loadModel = nil
}
//
@objc private func backPopClick(_ sender:UIButton) {
navigationController?.popViewController(animated: true)
}
}
//MARK: - UITextFieldDelegate
extension MPPositive_SearchResultShowViewController:UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let text = (textField.text! as NSString).replacingCharacters(in: range, with: string)
guard text.count <= 30 else {
return false
}
//,View
resultsShowView.isHidden = true
if text.isEmpty {
self.suggestionList = nil
cancelDebounceTimer()
}else {
//
loadSearchSuggestions(text)
}
return true
}
//
private func cancelDebounceTimer() {
debounceTimer?.invalidate()
debounceTimer = nil
}
//
private func loadSearchSuggestions(_ text:String) {
cancelDebounceTimer()
//0.8
debounceTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { [weak self] _ in
self?.searchText = text
self?.fetchSearchSuggestions(text)
}
}
//
private func fetchSearchSuggestions(_ text:String) {
MP_NetWorkManager.shared.requestSearchSuggestions(text) { [weak self] (result) in
self?.suggestionList = .init(self?.searchText ?? "", suggestions: result)
}
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
//textField
if let text = textField.text, text.isEmpty != true {
self.searchText = text
//
resultsShowView.loadModel = .init(text)
suggestionView.isHidden = true
//
view.endEditing(true)
return true
}else {
return false
}
}
}

View File

@ -14,67 +14,6 @@ class MPPositive_SearchViewController: MPPositive_BaseViewController {
imageView.contentMode = .scaleAspectFill
return imageView
}()
//textField
private lazy var searchTextField:UITextField = {
let textField = UITextField()
textField.delegate = self
textField.font = .systemFont(ofSize: 14*width, weight: .regular)
textField.textColor = .white
//
let attributedText = NSAttributedString(string: "Search songs,artists,playlists", attributes: [.font:UIFont.systemFont(ofSize: 14*width, weight: .regular), .foregroundColor:UIColor(hex: "#666666")])
textField.attributedPlaceholder = attributedText
return textField
}()
//tableView
private lazy var suggestionsTableView:UITableView = {
let tableView = UITableView()
tableView.backgroundColor = .init(hex: "#151718")
tableView.separatorStyle = .none
//
tableView.layer.borderWidth = 1*width
tableView.layer.borderColor = UIColor(hex: "#FFFFFF", alpha: 0.35).cgColor
tableView.layer.masksToBounds = true
tableView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
tableView.layer.cornerRadius = 10*width
tableView.rowHeight = 60*width
tableView.dataSource = self
tableView.delegate = self
tableView.register(MPPositive_SearchSuggestionItemTableViewCell.self, forCellReuseIdentifier: MPPositive_SearchSuggestionItemTableViewCellID)
tableView.register(MPPositive_SearchSuggestionListTableViewCell.self, forCellReuseIdentifier: MPPositive_SearchSuggestionListTableViewCellID)
return tableView
}()
private let MPPositive_SearchSuggestionItemTableViewCellID = "MPPositive_SearchSuggestionItemTableViewCell"
private let MPPositive_SearchSuggestionListTableViewCellID = "MPPositive_SearchSuggestionListTableViewCell"
//
private var suggestions:[[MPPositive_SearchSuggestionItemModel]]!{
willSet{
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
if newValue != nil {
//
var count:Int = 0
newValue.forEach { items in
count += items.count
}
suggestionsTableView.isHidden = false
suggestionsTableView.snp.updateConstraints { make in
make.height.equalTo(CGFloat(count*60)*width)
}
}else {
//
suggestionsTableView.isHidden = true
suggestionsTableView.snp.updateConstraints { make in
make.height.equalTo(0)
}
}
suggestionsTableView.reloadData()
}
}
}
//
private var debounceTimer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
setTitle("")
@ -86,9 +25,6 @@ class MPPositive_SearchViewController: MPPositive_BaseViewController {
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
searchTextField.text = ""
suggestionsTableView.isHidden = true
suggestions = nil
}
//
private func configure() {
@ -104,14 +40,6 @@ class MPPositive_SearchViewController: MPPositive_BaseViewController {
make.top.right.left.equalToSuperview()
make.height.equalTo(981*width)
}
view.addSubview(suggestionsTableView)
suggestionsTableView.snp.makeConstraints { make in
make.top.equalTo(searchView.snp.bottom)
make.left.equalTo(searchView.snp.left).offset(16*width)
make.right.equalTo(searchView.snp.right).offset(-16*width)
make.height.equalTo(240*width)
}
suggestionsTableView.isHidden = true
}
//
private func createSearchView() -> UIView{
@ -121,88 +49,27 @@ class MPPositive_SearchViewController: MPPositive_BaseViewController {
searchView.layer.masksToBounds = true
searchView.layer.cornerRadius = 16*width
//icon
let iconImageView = UIImageView(image: .init(named: "B_Seach"))
let iconImageView = UIImageView(image: .init(named: "Search_ICON'logo"))
searchView.addSubview(iconImageView)
iconImageView.snp.makeConstraints { make in
make.height.width.equalTo(16*width)
make.left.equalToSuperview().offset(16*width)
make.centerY.equalToSuperview()
}
//textField
searchView.addSubview(searchTextField)
searchTextField.snp.makeConstraints { make in
make.top.bottom.equalToSuperview()
let label = createLabel("Search songs,artists,playlists", font: .systemFont(ofSize: 14*width, weight: .regular), textColor: .init(hex: "#666666"), textAlignment: .left)
searchView.addSubview(label)
label.snp.makeConstraints { make in
make.left.equalTo(iconImageView.snp.right).offset(8*width)
make.right.equalToSuperview().offset(-16*width)
make.centerY.equalToSuperview()
}
searchView.isUserInteractionEnabled = true
searchView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(searchClick(_:))))
return searchView
}
//
@objc fileprivate func searchClick(_ sender:UITapGestureRecognizer) {
let resultVC = MPPositive_SearchResultShowViewController()
navigationController?.pushViewController(resultVC, animated: false)
}
}
}
//MARK: - textField
extension MPPositive_SearchViewController:UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let text = (textField.text! as NSString).replacingCharacters(in: range, with: string)
guard text.count <= 30 else {
return false
}
if text.isEmpty {
suggestions = nil
}else {
//
loadSearchSuggestions(text)
}
return true
}
//
private func cancelDebounceTimer() {
debounceTimer?.invalidate()
debounceTimer = nil
}
//
private func loadSearchSuggestions(_ text:String) {
cancelDebounceTimer()
//0.8
debounceTimer = Timer.scheduledTimer(withTimeInterval: 0.8, repeats: false) { [weak self] _ in
self?.fetchSearchSuggestions(text)
}
}
//
private func fetchSearchSuggestions(_ text:String) {
MP_NetWorkManager.shared.requestSearchSuggestions(text) { [weak self] (result) in
self?.suggestions = result
}
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
//textField
if let text = textField.text, text.isEmpty != true {
//
let showVC = MPPositive_SearchResultShowViewController()
navigationController?.pushViewController(showVC, animated: true)
return true
}else {
return false
}
}
}
//MARK: - tableView
extension MPPositive_SearchViewController:UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return suggestions != nil ? suggestions.count:0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return suggestions != nil ? suggestions[section].count:0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if suggestions[indexPath.section][indexPath.row].reviewUrls != nil {
let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_SearchSuggestionListTableViewCellID, for: indexPath) as! MPPositive_SearchSuggestionListTableViewCell
cell.item = suggestions[indexPath.section][indexPath.row]
return cell
}else {
let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_SearchSuggestionItemTableViewCellID, for: indexPath) as! MPPositive_SearchSuggestionItemTableViewCell
cell.item = suggestions[indexPath.section][indexPath.row]
return cell
}
}
}

View File

@ -0,0 +1,114 @@
//
// MPPositive_SearchResultPreviewShowView.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/13.
//
import UIKit
///
class MPPositive_SearchResultPreviewShowView: UIView, JXSegmentedListContainerViewListDelegate {
//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_SearchResultShowTableViewCell.self, forCellReuseIdentifier: MPPositive_SearchResultShowTableViewCellID)
return tableView
}()
private let MPPositive_SearchResultShowTableViewCellID = "MPPositive_SearchResultShowTableViewCell"
private var sectionLists:[MPPositive_SearchResultListViewModel]!{
didSet{
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
if sectionLists != nil {
tableView.reloadData()
}
}
}
}
var scrollBlock:(() -> Void)?
var chooseMoreIndexBlock:((Int) -> Void)?
init(frame: CGRect, sectionLists:[MPPositive_SearchResultListViewModel]) {
super.init(frame: frame)
backgroundColor = .init(hex: "1A1A1A")
self.sectionLists = sectionLists
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
//MARK: -
func listView() -> UIView {
return self
}
//
private func configure() {
addSubview(tableView)
tableView.snp.makeConstraints { make in
make.left.top.right.bottom.equalToSuperview()
}
}
//
@objc private func moreFindClick(_ sender:UIButton) {
let selectedTag = sender.tag
if chooseMoreIndexBlock != nil {
chooseMoreIndexBlock!(selectedTag)
}
}
}
//MARK: - tableView
extension MPPositive_SearchResultPreviewShowView:UITableViewDataSource, UITableViewDelegate {
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
if scrollBlock != nil {
scrollBlock!()
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return sectionLists.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sectionLists[section].previewItemViews.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_SearchResultShowTableViewCellID, for: indexPath) as! MPPositive_SearchResultShowTableViewCell
cell.itemView = sectionLists[indexPath.section].previewItemViews[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let sectionView:UIView = .init(frame: .init(x: 0, y: 0, width: screen_Width, height: 50*width))
sectionView.backgroundColor = .init(hex: "#1A1A1A")
let label = createLabel(sectionLists[section].title, font: .systemFont(ofSize: 16*width, weight: .semibold), textColor: .white, textAlignment: .left)
sectionView.addSubview(label)
label.snp.makeConstraints { make in
make.left.equalToSuperview().offset(18*width)
make.centerY.equalToSuperview()
}
if section != 0 {
//
let btn:UIButton = .init()
btn.setBackgroundImage(UIImage(named: "Home_More'logo"), for: .normal)
btn.tag = section
btn.addTarget(self, action: #selector(moreFindClick(_ :)), for: .touchUpInside)
sectionView.addSubview(btn)
btn.snp.makeConstraints { make in
make.width.height.equalTo(24*width)
make.right.equalToSuperview().offset(-18*width)
make.centerY.equalToSuperview()
}
}
return sectionView
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50*width
}
}

View File

@ -0,0 +1,112 @@
//
// MPPositive_SearchResultShowTableViewCell.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/13.
//
import UIKit
class MPPositive_SearchResultShowTableViewCell: 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_SearchResultItemViewModel!{
didSet{
itemView.setImage(iconImageView)
titleLabel.text = itemView.title
subtitleLabel.text = itemView.subtitle
//
if itemView.item.itemType == .single {
moreBtn.isHidden = false
loadBtn.isHidden = false
}else {
moreBtn.isHidden = true
loadBtn.isHidden = true
}
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
backgroundColor = .clear
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
private func configure() {
contentView.addSubview(iconImageView)
iconImageView.snp.makeConstraints { make in
make.width.height.equalTo(60*width)
make.top.equalToSuperview().offset(8*width).priority(999)
make.bottom.equalToSuperview().offset(-8*width)
make.left.equalToSuperview().offset(18*width)
}
contentView.addSubview(moreBtn)
moreBtn.snp.makeConstraints { make in
make.width.height.equalTo(24*width)
make.centerY.equalTo(iconImageView.snp.centerY)
make.right.equalToSuperview().offset(-18*width)
}
contentView.addSubview(loadBtn)
loadBtn.snp.makeConstraints { make in
make.width.height.equalTo(24*width)
make.centerY.equalTo(iconImageView.snp.centerY)
make.right.equalToSuperview().offset(-54*width)
}
contentView.addSubview(titleLabel)
titleLabel.snp.makeConstraints { make in
make.top.equalTo(iconImageView.snp.top).offset(10*width)
make.left.equalTo(iconImageView.snp.right).offset(12*width)
make.right.equalTo(loadBtn.snp.left).offset(-10*width)
}
contentView.addSubview(subtitleLabel)
subtitleLabel.snp.makeConstraints { make in
make.bottom.equalTo(iconImageView.snp.bottom).offset(-10*width)
make.left.equalTo(titleLabel.snp.left)
make.right.equalTo(titleLabel.snp.right)
}
}
//
@objc private func moreActionClick(_ sender:UIButton) {
}
//
@objc private func loadActionClick(_ sender:UIButton) {
}
}

View File

@ -0,0 +1,89 @@
//
// MPPositive_SearchResultTypeShowView.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/14.
//
import UIKit
///
class MPPositive_SearchResultTypeShowView: UIView, JXSegmentedListContainerViewListDelegate {
//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_SearchResultShowTableViewCell.self, forCellReuseIdentifier: MPPositive_SearchResultShowTableViewCellID)
return tableView
}()
private let MPPositive_SearchResultShowTableViewCellID = "MPPositive_SearchResultShowTableViewCell"
//
private 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()
}
}else {
tableView.reloadData()
}
}
}
}
var scrollBlock:(() -> Void)?
init(frame: CGRect, sectionList:MPPositive_SearchResultListViewModel) {
super.init(frame: frame)
backgroundColor = .init(hex: "1A1A1A")
self.sectionList = sectionList
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
//MARK: -
func listView() -> UIView {
return self
}
//
private func configure() {
addSubview(tableView)
tableView.snp.makeConstraints { make in
make.left.top.right.bottom.equalToSuperview()
}
}
}
//MARK: - tableView
extension MPPositive_SearchResultTypeShowView:UITableViewDataSource, UITableViewDelegate {
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
if scrollBlock != nil {
scrollBlock!()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sectionList?.allItemViews.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_SearchResultShowTableViewCellID, for: indexPath) as! MPPositive_SearchResultShowTableViewCell
cell.itemView = sectionList.allItemViews[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 0
}
}

View File

@ -0,0 +1,136 @@
//
// MPPositive_SearchResultsShowView.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/13.
//
import UIKit
class MPPositive_SearchResultsShowView: UIView {
///
var loadModel:MPPositive_LoadSearchResultsViewModel!{
didSet{
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
if loadModel == nil {
isHidden = true
}else {
loadModel.resultReloadBlock = {
[weak self] in
//
guard let self = self else {return}
isHidden = false
dataSource.titles = loadModel?.sectionLists.compactMap({$0.title}) ?? []
dataSource.reloadData(selectedIndex: 0)
segmentView.reloadData()
}
}
}
}
}
//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
return indicator
}()
//
fileprivate lazy var listContainerView:JXSegmentedListContainerView = {
let listContainerView = JXSegmentedListContainerView(dataSource: self)
listContainerView.backgroundColor = .clear
return listContainerView
}()
var scrollBlock:(() -> Void)?
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .clear
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
//
private func configure() {
segmentView.indicators = [indicator]
segmentView.dataSource = dataSource
//View
addSubview(segmentView)
segmentView.snp.makeConstraints { make in
make.left.right.equalToSuperview()
make.top.equalToSuperview()
make.height.equalTo(60*width)
}
addSubview(listContainerView)
listContainerView.snp.makeConstraints { (make) in
make.left.right.bottom.equalToSuperview().priority(999)
make.top.equalTo(segmentView.snp.bottom).priority(999)
}
segmentView.contentScrollView = listContainerView.scrollView
segmentView.listContainer = listContainerView
}
}
//MARK: - JXSegmentedTitleDataSource
extension MPPositive_SearchResultsShowView: JXSegmentedListContainerViewDataSource, JXSegmentedViewDelegate {
func numberOfLists(in listContainerView: JXSegmentedListContainerView) -> Int {
return dataSource.titles.count
}
func listContainerView(_ listContainerView: JXSegmentedListContainerView, initListAt index: Int) -> JXSegmentedListContainerViewListDelegate {
if index == 0 {
//
let showView:MPPositive_SearchResultPreviewShowView = .init(frame: listContainerView.frame, sectionLists: loadModel.sectionLists)
showView.scrollBlock = {
[weak self] in
if self?.scrollBlock != nil {
self?.scrollBlock!()
}
}
showView.chooseMoreIndexBlock = {
[weak self] (selectedIndex) in
guard let self = self else {
return
}
segmentView.selectItemAt(index: selectedIndex)
}
return showView
}else {
//
let showView:MPPositive_SearchResultTypeShowView = .init(frame: listContainerView.frame, sectionList: loadModel.sectionLists[index])
showView.scrollBlock = {
[weak self] in
if self?.scrollBlock != nil {
self?.scrollBlock!()
}
}
return showView
}
}
}

View File

@ -8,12 +8,10 @@
import UIKit
class MPPositive_SearchSuggestionItemTableViewCell: UITableViewCell {
//Icon
private lazy var iconImageView:UIImageView = UIImageView(image: .init(named: "B_Seach"))
private lazy var titleLabel:UILabel = createLabel(font: .systemFont(ofSize: 12*width, weight: .regular), textColor: .init(hex: "#FFFFFF", alpha: 0.6), textAlignment: .left)
var item:MPPositive_SearchSuggestionItemModel!{
private lazy var titleLabel:UILabel = createLabel(font: .systemFont(ofSize: 14*width, weight: .medium), textColor: .white, textAlignment: .left)
var item:NSAttributedString!{
didSet{
titleLabel.text = item.title ?? ""
titleLabel.attributedText = item
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
@ -36,17 +34,11 @@ class MPPositive_SearchSuggestionItemTableViewCell: UITableViewCell {
// Configure the view for the selected state
}
private func configure() {
contentView.addSubview(iconImageView)
iconImageView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(12*width)
make.width.height.equalTo(30*width)
make.centerY.equalToSuperview()
}
contentView.addSubview(titleLabel)
titleLabel.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.left.equalTo(iconImageView.snp.right).offset(10*width)
make.right.equalToSuperview().offset(-12*width)
make.left.equalToSuperview().offset(18*width)
make.right.equalToSuperview().offset(-18*width)
}
}
}

View File

@ -1,65 +0,0 @@
//
// MPPositive_SearchSuggestionListTableViewCell.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/12.
//
import UIKit
import Kingfisher
class MPPositive_SearchSuggestionListTableViewCell: UITableViewCell {
//Icon
private lazy var reviewImageView:UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
return imageView
}()
private lazy var titleLabel:UILabel = createLabel(font: .systemFont(ofSize: 12*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)
var item:MPPositive_SearchSuggestionItemModel!{
didSet{
reviewImageView.kf.setImage(with: URL(string: item.reviewUrls?.last ?? ""), placeholder: placeholderImage)
titleLabel.text = item.title ?? ""
subtitleLabel.text = item.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(reviewImageView)
reviewImageView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(12*width)
make.width.height.equalTo(30*width)
make.centerY.equalToSuperview()
}
contentView.addSubview(titleLabel)
titleLabel.snp.makeConstraints { make in
make.top.equalTo(reviewImageView.snp.top)
make.left.equalTo(reviewImageView.snp.right).offset(10*width)
make.right.equalToSuperview().offset(-12*width)
}
contentView.addSubview(subtitleLabel)
subtitleLabel.snp.makeConstraints { make in
make.bottom.equalTo(reviewImageView.snp.bottom)
make.left.equalTo(titleLabel.snp.left)
make.right.equalTo(titleLabel.snp.right)
}
}
}

View File

@ -0,0 +1,60 @@
//
// MPPositive_SearchSuggestionsView.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/13.
//
import UIKit
///
class MPPositive_SearchSuggestionsView: UIView {
//tableView
private lazy var tableView:UITableView = {
let tableView = UITableView()
tableView.backgroundColor = .clear
tableView.separatorStyle = .none
tableView.rowHeight = 75*width
tableView.dataSource = self
tableView.delegate = self
tableView.register(MPPositive_SearchSuggestionItemTableViewCell.self, forCellReuseIdentifier: MPPositive_SearchSuggestionItemTableViewCellID)
return tableView
}()
private let MPPositive_SearchSuggestionItemTableViewCellID = "MPPositive_SearchSuggestionItemTableViewCell"
//
var suggestions:[NSAttributedString]!{
didSet{
//
tableView.reloadData()
}
}
var selectedTextBlock:((String) -> Void)?
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .clear
addSubview(tableView)
tableView.snp.makeConstraints { make in
make.left.right.bottom.top.equalToSuperview()
}
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}
//MARK: - tableView
extension MPPositive_SearchSuggestionsView:UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return suggestions != nil ? suggestions.count:0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_SearchSuggestionItemTableViewCellID, for: indexPath) as! MPPositive_SearchSuggestionItemTableViewCell
cell.item = suggestions[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if selectedTextBlock != nil {
let text = suggestions[indexPath.row].string
selectedTextBlock!(text)
}
}
}

View File

@ -18,4 +18,7 @@ pod 'SVProgressHUD'
pod 'Alamofire'
#网络图片实现
pod "Kingfisher"
#分页工具
pod 'JXSegmentedView'
pod 'JXPagingView/Paging'
end

View File

@ -1,6 +1,8 @@
PODS:
- Alamofire (5.9.1)
- IQKeyboardManagerSwift (6.5.13)
- JXPagingView/Paging (2.1.3)
- JXSegmentedView (1.3.3)
- Kingfisher (7.11.0)
- SnapKit (5.6.0)
- SVProgressHUD (2.2.5)
@ -9,6 +11,8 @@ PODS:
DEPENDENCIES:
- Alamofire
- IQKeyboardManagerSwift
- JXPagingView/Paging
- JXSegmentedView
- Kingfisher
- SnapKit
- SVProgressHUD
@ -18,6 +22,8 @@ SPEC REPOS:
trunk:
- Alamofire
- IQKeyboardManagerSwift
- JXPagingView
- JXSegmentedView
- Kingfisher
- SnapKit
- SVProgressHUD
@ -26,11 +32,13 @@ SPEC REPOS:
SPEC CHECKSUMS:
Alamofire: f36a35757af4587d8e4f4bfa223ad10be2422b8c
IQKeyboardManagerSwift: 69b5fb9960edff37263d45c44fd97427e1f8c22b
JXPagingView: afdd2e9af09c90160dd232b970d603cc6e7ddd0e
JXSegmentedView: 651b60fcf705258ba9395edd53876dbd2853fb68
Kingfisher: b9c985d864d43515f404f1ef4a8ce7d802ace3ac
SnapKit: e01d52ebb8ddbc333eefe2132acf85c8227d9c25
SVProgressHUD: 1428aafac632c1f86f62aa4243ec12008d7a51d6
SwiftDate: 72d28954e8e1c6c1c0f917ccc8005e4f83c7d4b2
PODFILE CHECKSUM: d3eda313abd465652ca5c62aaab483d8f02ed2d2
PODFILE CHECKSUM: c0bbca53cdc7c53ea7a9dbc8d75b7ae964a1dbc2
COCOAPODS: 1.15.2

21
Pods/JXPagingView/LICENSE generated Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 暴走的鑫鑫
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.

294
Pods/JXPagingView/README.md generated Normal file
View File

@ -0,0 +1,294 @@
# JXPagingView
类似微博主页、简书主页、QQ联系人页面等效果。多页面嵌套既可以上下滑动也可以左右滑动切换页面。支持HeaderView悬浮、支持下拉刷新、上拉加载更多。
## 功能特点
- 支持OC与Swift;
- 支持列表懒加载,等到列表真正显示的时候才加载,而不是一次性加载所有列表;
- 支持首页下拉刷新、列表视图下拉刷新、列表视图上拉加载更多;
- 支持悬浮SectionHeader的垂直位置调整
- 支持从顶部用力往上滚动,下面的列表会跟着滚动,而不会突然卡主,需要使用`JXPagerSmoothView`类;
- 列表封装简洁,只要遵从`JXPagingViewListViewDelegate`协议即可。UIView、UIViewController等都可以
- 使用JXCategoryView/JXSegmentedView分类控制器几乎支持所有主流效果、高度自定义、可灵活扩展
- 支持横竖屏切换;
- 支持点击状态栏滚动当前列表到顶部;
- 支持列表显示和消失的生命周期方法;
- isListHorizontalScrollEnabled属性控制列表是否可以左右滑动默认YES
- 支持`FDFullscreenPopGesture`等全屏手势兼容处理;
## 预览
| 效果 | 预览图 |
|-------|-------|
| **头图缩放** <br/>参考[ZoomViewController](https://github.com/pujiaxin33/JXPagingView/tree/master/Examples/JXPagerViewExample-OC/JXPagerViewExample-OC/Example/Zoom/ZoomViewController.m)类 | ![Zoom](https://github.com/pujiaxin33/JXExampleImages/blob/master/JXPaingView/Zoom.gif) |
| **主页下拉刷新&列表上拉加载更多** <br/>参考[RefreshViewController](https://github.com/pujiaxin33/JXPagingView/tree/master/Examples/JXPagerViewExample-OC/JXPagerViewExample-OC/Example/Refresh/RefreshViewController.m)类 | ![Refresh](https://github.com/pujiaxin33/JXExampleImages/blob/master/JXPaingView/Refresh.gif) |
| **列表下拉刷新** <br/>参考[ListRefreshViewController](https://github.com/pujiaxin33/JXPagingView/tree/master/Examples/JXPagerViewExample-OC/JXPagerViewExample-OC/Example/Refresh/ListRefreshViewController.m)类 | ![Refresh](https://github.com/pujiaxin33/JXExampleImages/blob/master/JXPaingView/ListRefresh.gif) |
| **悬浮sectionHeader位置调整** | ![Refresh](https://github.com/pujiaxin33/JXExampleImages/blob/master/JXPaingView/PinSectionHeaderPosition.gif) |
| **导航栏隐藏** <br/> 参考[NaviBarHiddenViewController](https://github.com/pujiaxin33/JXPagingView/tree/master/Examples/JXPagerViewExample-OC/JXPagerViewExample-OC/Example/NavigationBarHidden/NaviBarHiddenViewController.m)类 | ![Refresh](https://github.com/pujiaxin33/JXExampleImages/blob/master/JXPaingView/NaviHidden.gif) |
| **CollectionView列表示例**<br/>参考[CollectionViewViewController.swift](https://github.com/pujiaxin33/JXPagingView/tree/master/Examples/JXPagingViewExample/JXPagingViewExample/Example/CollectionView/CollectionViewViewController.swift)类 <br/> 只有swift的demo工程有该示例 | ![Refresh](https://github.com/pujiaxin33/JXExampleImages/blob/master/JXPaingView/CollectionViewList.gif) |
| **HeaderView更新高度示例**<br/> 参考[HeightChangeAnimationViewController.swift](https://github.com/pujiaxin33/JXPagingView/tree/master/Examples/JXPagingViewExample/JXPagingViewExample/Example/HeightChange/HeightChangeAnimationViewController.swift)类 <br/> 只有swift demo工程才有该示例 | ![Refresh](https://github.com/pujiaxin33/JXExampleImages/blob/master/JXPaingView/HeaderViewHeightChange.gif) |
| **PagingView嵌套CategoryView** <br/> 参考[NestViewController](https://github.com/pujiaxin33/JXPagingView/tree/master/Examples/JXPagerViewExample-OC/JXPagerViewExample-OC/Example/Nest/NestViewController.m)类 <br/> 只有 **OC!OC!OC!** 的demo工程才有该示例 <br/> 操作比较特殊,如果需要此效果,<br/> 请认真参考源码,有问题多试试 <br/> 参考NestViewController.h类 | ![Nest](https://github.com/pujiaxin33/JXExampleImages/blob/master/JXPaingView/Nest.gif) |
| **CategoryView嵌套PagingView** <br/> 参考[NestViewController.swift](https://github.com/pujiaxin33/JXPagingView/tree/master/Examples/JXPagingViewExample/JXPagingViewExample/Example/CategoryNestPaging/NestViewController.swift)类 <br/> 只有 **Swift!Swift!Swift!** 的demo工程才有该示例 <br/> 操作比较特殊,如果需要此效果,<br/> 请认真参考源码,有问题多试试 <br/> 参考NestViewController.swift类 | ![Nest](https://github.com/pujiaxin33/JXExampleImages/blob/master/JXPaingView/CategoryNestPaging.gif) |
| **点击状态栏** | ![Zoom](https://github.com/pujiaxin33/JXExampleImages/blob/master/JXPaingView/StatusBarClicked.gif) |
| **横竖屏旋转** | ![Zoom](https://github.com/pujiaxin33/JXExampleImages/blob/master/JXPaingView/ScreenRotate.gif) |
| **JXPageListView**<br/> 顶部需要自定义cell的场景类似于电商APP首页滑动到列表最底部才是分类控制器 <br/> 该效果是另一个库,点击查看[JXPageListView](https://github.com/pujiaxin33/JXPageListView) <br/> 该效果是另一个库,点击查看[JXPageListView](https://github.com/pujiaxin33/JXPageListView) <br/> 该效果是另一个库,点击查看[JXPageListView](https://github.com/pujiaxin33/JXPageListView) | ![list](https://github.com/pujiaxin33/JXPageListView/blob/master/JXPageListView/Gif/headerLoading.gif) |
| **JXPagerSmoothView**<br/> 类似淘宝、转转首页 <br/> 从顶部用力往上滚动,下面的列表会继续滚动 | ![smooth](https://github.com/pujiaxin33/JXExampleImages/blob/master/JXPaingView/smooth.gif) |
## 安装
### 手动
**Swift版本** Clone代码拖入JXPagingView-Swift文件夹使用`JXPagingView`类;
**OC版本** Clone代码拖入JXPagerView文件夹使用`JXPagerView`类;
### CocoaPods
- **Swift版本**
支持swift版本5.0+
```ruby
target '<Your Target Name>' do
pod 'JXPagingView/Paging'
end
```
- **OC版本**
```ruby
target '<Your Target Name>' do
pod 'JXPagingView/Pager'
end
```
Swift与OC的仓库地址不一样请注意选择
先`pod repo update`然后再`pod install`
## 使用
swift版本使用类似只是类名及相关API更改为`JXPagingView`具体细节请查看Swfit工程。
### 1、初始化`JXCategoryTitleView`和`JXPagerView`
```Objective-C
self.categoryView = [[JXCategoryTitleView alloc] initWithFrame:frame];
//配置categoryView细节参考源码
self.pagerView = [[JXPagerView alloc] initWithDelegate:self];
[self.view addSubview:self.pagerView];
//⚠将pagerView的listContainerView和categoryView.listContainer进行关联这样列表就可以和categoryView联动了。⚠
self.categoryView.listContainer = (id<JXCategoryViewListContainer>)self.pagerView.listContainerView;
```
**Swift版本列表关联代码**
```Swift
//给JXPagingListContainerView添加extension表示遵从JXSegmentedViewListContainer的协议
extension JXPagingListContainerView: JXSegmentedViewListContainer {}
//⚠将pagingView的listContainerView和segmentedView.listContainer进行关联这样列表就可以和categoryView联动了。⚠
segmentedView.listContainer = pagingView.listContainerView
```
### 2、实现`JXPagerViewDelegate`协议
```Objective-C
/**
返回tableHeaderView的高度因为内部需要比对判断只能是整型数
*/
- (NSUInteger)tableHeaderViewHeightInPagerView:(JXPagerView *)pagerView {
return JXTableHeaderViewHeight;
}
/**
返回tableHeaderView
*/
- (UIView *)tableHeaderViewInPagerView:(JXPagerView *)pagerView {
return self.userHeaderView;
}
/**
返回悬浮HeaderView的高度因为内部需要比对判断只能是整型数
*/
- (NSUInteger)heightForPinSectionHeaderInPagerView:(JXPagerView *)pagerView {
return JXheightForHeaderInSection;
}
/**
返回悬浮HeaderView
*/
- (UIView *)viewForPinSectionHeaderInPagerView:(JXPagerView *)pagerView {
return self.categoryView;
}
/**
返回列表的数量
*/
- (NSInteger)numberOfListsInPagerView:(JXPagerView *)pagerView {
//和categoryView的item数量一致
return self.titles.count;
}
/**
根据index初始化一个对应列表实例。注意一定要是新生成的实例
只要遵循JXPagerViewListViewDelegate即可无论你返回的是UIView还是UIViewController都可以。
*/
- (id<JXPagerViewListViewDelegate>)pagerView:(JXPagerView *)pagerView initListAtIndex:(NSInteger)index {
TestListBaseView *listView = [[TestListBaseView alloc] init];
if (index == 0) {
listView.dataSource = @[@"橡胶火箭", @"橡胶火箭炮", @"橡胶机关枪"...].mutableCopy;
}else if (index == 1) {
listView.dataSource = @[@"吃烤肉", @"吃鸡腿肉", @"吃牛肉", @"各种肉"].mutableCopy;
}else {
listView.dataSource = @[@"【剑士】罗罗诺亚·索隆", @"【航海士】娜美", @"【狙击手】乌索普"...].mutableCopy;
}
[listView beginFirstRefresh];
return listView;
}
```
### 3、实现`JXPagerViewListViewDelegate`协议
列表可以是任意类UIView、UIViewController等等都可以只要实现了`JXPagerViewListViewDelegate`协议就行。
⚠️⚠️⚠️一定要保证`scrollCallback`的正确回调,许多朋友都容易疏忽这一点,导致异常,务必重点注意!
下面的使用代码参考的是`TestListBaseView`类
```Objective-C
/**
返回listView。如果是vc包裹的就是vc.view如果是自定义view包裹的就是自定义view自己。
*/
- (UIView *)listView {
return self;
}
/**
返回listView内部持有的UIScrollView或UITableView或UICollectionView
主要用于mainTableView已经显示了headerlistView的contentOffset需要重置时内部需要访问到外部传入进来的listView内的scrollView
*/
- (UIScrollView *)listScrollView {
return self.tableView;
}
/**
当listView内部持有的UIScrollView或UITableView或UICollectionView的代理方法`scrollViewDidScroll`回调时需要调用该代理方法传入的callback
*/
- (void)listViewDidScrollCallback:(void (^)(UIScrollView *))callback {
self.scrollCallback = callback;
}
```
### 4、列表回调处理
`TestListBaseView`在其`tableView`的滚动回调中通过调用上面持有的scrollCallback把列表的滚动事件回调给JXPagerView内部。
```Objective-C
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
!self.scrollCallback ?: self.scrollCallback(scrollView);
}
```
## 实现原理
[实现原理](https://github.com/pujiaxin33/JXPagingView/blob/master/Document/JXPagingView%E5%8E%9F%E7%90%86.md)
## `JXPagerSmoothView`
如果你需要类似于**淘宝**、**转转**首页从顶部header用力往上滚动之后下面的列表会跟着滚动的效果。因为`JXPagerView`的实现原理限制当用户从顶部header的位置用力往上滚动`JXPagerView`会在`JXCategoryView`刚好在顶部的时候突然停住。这个时候就需要使用`JXPagerSmoothView`swift版本叫`JXPagingSmoothView`。
因为与`JXPagerView`的原理完全不同所以各自会有一些特性的区别但是从使用体验来说是完全一致的。具体使用细节请参考demo示例。
实现原理参考[JXPagerSmoothView文章解析](https://juejin.im/post/5ddb2fe4f265da7def5424c7)
## 特殊说明
### JXCategoryView、JXSegmentedView
悬浮的HeaderView用的是我写的[OC版本-JXCategoryView](https://github.com/pujiaxin33/JXCategoryView) 、[Swift版本-JXSegmentedView](https://github.com/pujiaxin33/JXSegmentedView)。几乎实现了所有主流效果,而且非常容易自定义扩展,强烈推荐阅读。
### 头图缩放说明
头图缩放原理,参考这个库:[JXTableViewZoomHeaderImageView](https://github.com/pujiaxin33/JXTableViewZoomHeaderImageView)
### 列表下拉刷新说明
需要使用`JXPagerListRefreshView`类(是`JXPagerView`的子类)
### JXPagerListContainerType说明
UIScrollView优势没有其他副作用。劣势实时的视图内存占用相对大一点因为所有加载之后的列表视图都在视图层级里面。
UICollectionView优势因为列表被添加到cell上实时的视图内存占用更少适合内存要求特别高的场景。劣势因为cell重用机制的问题导致列表被移除屏幕外之后会被放入缓存区而不存在于视图层级中。如果刚好你的列表使用了下拉刷新视图在快速切换过程中就会导致下拉刷新回调不成功的问题。使用MJRefresh会出现此问题一句话概括使用CollectionView的时候就不要让列表使用下拉刷新加载。
### 关于下方列表视图的代理方法`- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath`有时候需要点击两次才回调
出现步骤当手指放在下方列表视图往下拉直到TableHeaderView完全显示。
原因经过上面的步骤之后手指已经离开屏幕且列表视图已经完全静止UIScrollView的isDragging属性却依然是true。就导致了后续的第一次点击让系统认为当前UIScrollView依然在滚动该点击就让UIScrollView停止下来没有继续转发给UITableView就没有转化成didSelectRow事件。
解决方案经过N种尝试之后还是没有回避掉系统的`isDragging`异常为true的bug。大家可以在自定义cell最下方放置一个与cell同大小的button把button的touchUpInside事件当做`didSelectRow`的回调。因为UIButton在响应链中的优先级要高于UIGestureRecognizer。
代码:请参考`TestTableViewCell`类的配置。
### 指定默认选中index
默认显示index=2的列表代码如下
```
self.pagerView.defaultSelectedIndex = 2;
self.categoryView.defaultSelectedIndex = 2;
```
### 顶部轮播图手势处理
如果TableHeaderView添加了轮播图获取其他可以横向滚动的UIScrollView。如果不处理就会出现左右滚动轮播图的时候又可以触发整个页面的上下滚动。为了规避该问题请参考示例仓库中`BannerViewController`类的处理方法。即可同一时间只允许左右滚动或者上下滚动。
### 关于列表用UIViewController封装且要支持横竖屏的tips
在列表UIViewController类里面一定要加上下面这段代码(不要问我为什么,我也不知道,谁知道系统内部是怎么操作的,反正加上就没毛病了)
```
- (void)loadView {
self.view = [[UIView alloc] init];
}
```
### `JXPagerSmoothView` header有UITextField或者`UITextView`
详情参考OC版本示例【滚动延续 Header有输入框】
列表自定义子类化`UITableView`或者`UICollectionView`,然后重载`scrollRectToVisible`方法,示例代码如下。
```Object-C
@implementation TestTableView
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated {
[self setContentOffset:CGPointMake(self.contentOffset.x, rect.origin.y) animated:animated];
}
@end
```
### `FDFullscreenPopGesture`等全屏手势兼容处理
[全屏手势兼容处理文档,点击查看 ❗️❗️❗️](https://github.com/pujiaxin33/JXPagingView/blob/master/Document/%E5%85%A8%E5%B1%8F%E6%89%8B%E5%8A%BF%E5%A4%84%E7%90%86.md)
## 迁移指南
- **0.0.9版本**将下面两个API的返回值修改为了NSUInteger(swift版本为Int)之前版本是CGFloat升级为0.0.9及以上的时候记得修改一下使用地方的返回值类型不然会引起crash。
- `- (NSUInteger)heightForPinSectionHeaderInPagerView:(JXPagerView *)pagerView`
- `- (NSUInteger)tableHeaderViewHeightInPagerView:(JXPagerView *)pagerView`
- **1.0.0版本**
删除代理方法`- (NSArray <id<JXPagerViewListViewDelegate>> *)listViewsInPagerView:(JXPagerView *)pagerView;`,请参考示例使用下面两个代理方法:
- `- (NSInteger)numberOfListsInPagerView:(JXPagerView *)pagerView;`
- `- (id<JXPagerViewListViewDelegate>)pagerView:(JXPagerView *)pagerView initListAtIndex:(NSInteger)index;`
- **2.0.0版本**`JXPagerListContainerView`进行了重构,列表拥有了完整的生命周期方法。列表是`UIViewController`类,`viewWillAppear`等生命周期方法将会正确触发。
-
- 删除了collectionView用`scrollView`属性替换。
- 和`CategoryView`的联动绑定代码更新为`self.categoryView.listContainer = (id<JXCategoryViewListContainer>)self.pagerView.listContainerView;`。
- `JXPagerView`新增`- (instancetype)initWithDelegate:(id<JXPagerViewDelegate>)delegate listContainerType:(JXPagerListContainerType)type`初始化方法,可以指定列表容器为`UIScrollView`或者`UICollectionView`
## 补充
有不明白的地方建议多看下源码。再有疑问的欢迎提Issue交流🤝

View File

@ -0,0 +1,619 @@
//
// JXPagingListContainerView.swift
// JXSegmentedView
//
// Created by jiaxin on 2018/12/26.
// Copyright © 2018 jiaxin. All rights reserved.
//
import UIKit
///
///- ScrollView: UIScrollView
/// - CollectionView: 使UICollectionViewcellcell使使CollectionView使
public enum JXPagingListContainerType {
case scrollView
case collectionView
}
public protocol JXPagingViewListViewDelegate: NSObjectProtocol {
/// VCVC.view
/// ViewView
///
/// - Returns:
func listView() -> UIView
/// listViewUIScrollViewUITableViewUICollectionView
/// mainTableViewheaderlistViewcontentOffset访listViewscrollView
///
/// - Returns: listViewUIScrollViewUITableViewUICollectionView
func listScrollView() -> UIScrollView
/// listViewUIScrollViewUITableViewUICollectionView`scrollViewDidScroll`callback
///
/// - Parameter callback: `scrollViewDidScroll`callback
func listViewDidScrollCallback(callback: @escaping (UIScrollView)->())
/// listScrollViewcontentOffset
func listScrollViewWillResetContentOffset()
///
func listWillAppear()
///
func listDidAppear()
///
func listWillDisappear()
///
func listDidDisappear()
}
public extension JXPagingViewListViewDelegate {
func listScrollViewWillResetContentOffset() {}
func listWillAppear() {}
func listDidAppear() {}
func listWillDisappear() {}
func listDidDisappear() {}
}
public protocol JXPagingListContainerViewDataSource: NSObjectProtocol {
/// list
///
/// - Parameter listContainerView: JXPagingListContainerView
func numberOfLists(in listContainerView: JXPagingListContainerView) -> Int
/// index`JXPagingViewListViewDelegate`
/// UIViewUIView`JXPagingViewListViewDelegate`UIView
/// UIViewControllerUIViewController`JXPagingViewListViewDelegate`UIViewController
///
///
/// - Parameters:
/// - listContainerView: JXPagingListContainerView
/// - index: index
/// - Returns: JXPagingViewListViewDelegate
func listContainerView(_ listContainerView: JXPagingListContainerView, initListAt index: Int) -> JXPagingViewListViewDelegate
/// index
func listContainerView(_ listContainerView: JXPagingListContainerView, canInitListAt index: Int) -> Bool
/// UIScrollViewUICollectionViewClass
/// UIScrollViewFDFullscreenPopGesture
///
/// - Parameter listContainerView: JXPagingListContainerView
/// - Returns: UIScrollView
func scrollViewClass(in listContainerView: JXPagingListContainerView) -> AnyClass?
}
public extension JXPagingListContainerViewDataSource {
func listContainerView(_ listContainerView: JXPagingListContainerView, canInitListAt index: Int) -> Bool { true }
func scrollViewClass(in listContainerView: JXPagingListContainerView) -> AnyClass? { nil }
}
protocol JXPagingListContainerViewDelegate: NSObjectProtocol {
func listContainerViewDidScroll(_ listContainerView: JXPagingListContainerView)
func listContainerViewWillBeginDragging(_ listContainerView: JXPagingListContainerView)
func listContainerViewDidEndScrolling(_ listContainerView: JXPagingListContainerView)
func listContainerView(_ listContainerView: JXPagingListContainerView, listDidAppearAt index: Int)
}
extension JXPagingListContainerViewDelegate {
func listContainerViewDidScroll(_ listContainerView: JXPagingListContainerView) {}
func listContainerViewWillBeginDragging(_ listContainerView: JXPagingListContainerView) {}
func listContainerViewDidEndScrolling(_ listContainerView: JXPagingListContainerView) {}
func listContainerView(_ listContainerView: JXPagingListContainerView, listDidAppearAt index: Int) {}
}
open class JXPagingListContainerView: UIView {
public private(set) var type: JXPagingListContainerType
public private(set) weak var dataSource: JXPagingListContainerViewDataSource?
public private(set) var scrollView: UIScrollView!
public var isCategoryNestPagingEnabled = false {
didSet {
if let containerScrollView = scrollView as? JXPagingListContainerScrollView {
containerScrollView.isCategoryNestPagingEnabled = isCategoryNestPagingEnabled
}else if let containerScrollView = scrollView as? JXPagingListContainerCollectionView {
containerScrollView.isCategoryNestPagingEnabled = isCategoryNestPagingEnabled
}
}
}
/// keyindexvalue
open var validListDict = [Int:JXPagingViewListViewDelegate]()
/// 0.010~101
open var initListPercent: CGFloat = 0.01 {
didSet {
if initListPercent <= 0 || initListPercent >= 1 {
assertionFailure("initListPercent值范围为开区间(0,1)即不包括0和1")
}
}
}
public var listCellBackgroundColor: UIColor = .white
/// segmentedView.defaultSelectedIndexindex
public var defaultSelectedIndex: Int = 0 {
didSet {
currentIndex = defaultSelectedIndex
}
}
weak var delegate: JXPagingListContainerViewDelegate?
public private(set) var currentIndex: Int = 0
private var collectionView: UICollectionView!
private var containerVC: JXPagingListContainerViewController!
private var willAppearIndex: Int = -1
private var willDisappearIndex: Int = -1
public init(dataSource: JXPagingListContainerViewDataSource, type: JXPagingListContainerType = .collectionView) {
self.dataSource = dataSource
self.type = type
super.init(frame: CGRect.zero)
commonInit()
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
open func commonInit() {
guard let dataSource = dataSource else { return }
containerVC = JXPagingListContainerViewController()
containerVC.view.backgroundColor = .clear
addSubview(containerVC.view)
containerVC.viewWillAppearClosure = {[weak self] in
self?.listWillAppear(at: self?.currentIndex ?? 0)
}
containerVC.viewDidAppearClosure = {[weak self] in
self?.listDidAppear(at: self?.currentIndex ?? 0)
}
containerVC.viewWillDisappearClosure = {[weak self] in
self?.listWillDisappear(at: self?.currentIndex ?? 0)
}
containerVC.viewDidDisappearClosure = {[weak self] in
self?.listDidDisappear(at: self?.currentIndex ?? 0)
}
if type == .scrollView {
if let scrollViewClass = dataSource.scrollViewClass(in: self) as? UIScrollView.Type {
scrollView = scrollViewClass.init()
}else {
scrollView = JXPagingListContainerScrollView.init()
}
scrollView.backgroundColor = .clear
scrollView.delegate = self
scrollView.isPagingEnabled = true
scrollView.showsVerticalScrollIndicator = false
scrollView.showsHorizontalScrollIndicator = false
scrollView.scrollsToTop = false
scrollView.bounces = false
if #available(iOS 11.0, *) {
scrollView.contentInsetAdjustmentBehavior = .never
}
containerVC.view.addSubview(scrollView)
}else if type == .collectionView {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
if let collectionViewClass = dataSource.scrollViewClass(in: self) as? UICollectionView.Type {
collectionView = collectionViewClass.init(frame: CGRect.zero, collectionViewLayout: layout)
}else {
collectionView = JXPagingListContainerCollectionView.init(frame: CGRect.zero, collectionViewLayout: layout)
}
collectionView.backgroundColor = .clear
collectionView.isPagingEnabled = true
collectionView.showsHorizontalScrollIndicator = false
collectionView.showsVerticalScrollIndicator = false
collectionView.scrollsToTop = false
collectionView.bounces = false
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
if #available(iOS 10.0, *) {
collectionView.isPrefetchingEnabled = false
}
if #available(iOS 11.0, *) {
self.collectionView.contentInsetAdjustmentBehavior = .never
}
containerVC.view.addSubview(collectionView)
//访scrollView
scrollView = collectionView
}
}
open override func willMove(toSuperview newSuperview: UIView?) {
super.willMove(toSuperview: newSuperview)
var next: UIResponder? = newSuperview
while next != nil {
if let vc = next as? UIViewController{
vc.addChild(containerVC)
break
}
next = next?.next
}
}
open override func layoutSubviews() {
super.layoutSubviews()
guard let dataSource = dataSource else { return }
containerVC.view.frame = bounds
if type == .scrollView {
if scrollView.frame == CGRect.zero || scrollView.bounds.size != bounds.size {
scrollView.frame = bounds
scrollView.contentSize = CGSize(width: scrollView.bounds.size.width*CGFloat(dataSource.numberOfLists(in: self)), height: scrollView.bounds.size.height)
for (index, list) in validListDict {
list.listView().frame = CGRect(x: CGFloat(index)*scrollView.bounds.size.width, y: 0, width: scrollView.bounds.size.width, height: scrollView.bounds.size.height)
}
scrollView.contentOffset = CGPoint(x: CGFloat(currentIndex)*scrollView.bounds.size.width, y: 0)
}else {
scrollView.frame = bounds
scrollView.contentSize = CGSize(width: scrollView.bounds.size.width*CGFloat(dataSource.numberOfLists(in: self)), height: scrollView.bounds.size.height)
}
}else {
if collectionView.frame == CGRect.zero || collectionView.bounds.size != bounds.size {
collectionView.frame = bounds
collectionView.collectionViewLayout.invalidateLayout()
collectionView.reloadData()
collectionView.setContentOffset(CGPoint(x: CGFloat(currentIndex)*collectionView.bounds.size.width, y: 0), animated: false)
}else {
collectionView.frame = bounds
}
}
}
//MARK: - JXSegmentedViewListContainer
public func contentScrollView() -> UIScrollView {
return scrollView
}
public func scrolling(from leftIndex: Int, to rightIndex: Int, percent: CGFloat, selectedIndex: Int) {
}
public func didClickSelectedItem(at index: Int) {
guard checkIndexValid(index) else {
return
}
willAppearIndex = -1
willDisappearIndex = -1
if currentIndex != index {
listWillDisappear(at: currentIndex)
listWillAppear(at: index)
listDidDisappear(at: currentIndex)
listDidAppear(at: index)
}
}
public func reloadData() {
guard let dataSource = dataSource else { return }
if currentIndex < 0 || currentIndex >= dataSource.numberOfLists(in: self) {
defaultSelectedIndex = 0
currentIndex = 0
}
validListDict.values.forEach { (list) in
if let listVC = list as? UIViewController {
listVC.removeFromParent()
}
list.listView().removeFromSuperview()
}
validListDict.removeAll()
if type == .scrollView {
scrollView.contentSize = CGSize(width: scrollView.bounds.size.width*CGFloat(dataSource.numberOfLists(in: self)), height: scrollView.bounds.size.height)
}else {
collectionView.reloadData()
}
listWillAppear(at: currentIndex)
listDidAppear(at: currentIndex)
}
//MARK: - Private
func initListIfNeeded(at index: Int) {
guard let dataSource = dataSource else { return }
if dataSource.listContainerView(self, canInitListAt: index) == false {
return
}
var existedList = validListDict[index]
if existedList != nil {
//
return
}
existedList = dataSource.listContainerView(self, initListAt: index)
guard let list = existedList else {
return
}
if let vc = list as? UIViewController {
containerVC.addChild(vc)
}
validListDict[index] = list
switch type {
case .scrollView:
list.listView().frame = CGRect(x: CGFloat(index)*scrollView.bounds.size.width, y: 0, width: scrollView.bounds.size.width, height: scrollView.bounds.size.height)
scrollView.addSubview(list.listView())
case .collectionView:
if let cell = collectionView.cellForItem(at: IndexPath(item: index, section: 0)) {
cell.contentView.subviews.forEach { $0.removeFromSuperview() }
list.listView().frame = cell.contentView.bounds
cell.contentView.addSubview(list.listView())
}
}
}
private func listWillAppear(at index: Int) {
guard let dataSource = dataSource else { return }
guard checkIndexValid(index) else {
return
}
var existedList = validListDict[index]
if existedList != nil {
existedList?.listWillAppear()
if let vc = existedList as? UIViewController {
vc.beginAppearanceTransition(true, animated: false)
}
}else {
//listWillAppear
guard dataSource.listContainerView(self, canInitListAt: index) != false else {
return
}
existedList = dataSource.listContainerView(self, initListAt: index)
guard let list = existedList else {
return
}
if let vc = list as? UIViewController {
containerVC.addChild(vc)
}
validListDict[index] = list
if type == .scrollView {
if list.listView().superview == nil {
list.listView().frame = CGRect(x: CGFloat(index)*scrollView.bounds.size.width, y: 0, width: scrollView.bounds.size.width, height: scrollView.bounds.size.height)
scrollView.addSubview(list.listView())
}
list.listWillAppear()
if let vc = list as? UIViewController {
vc.beginAppearanceTransition(true, animated: false)
}
}else {
let cell = collectionView.cellForItem(at: IndexPath(item: index, section: 0))
cell?.contentView.subviews.forEach { $0.removeFromSuperview() }
list.listView().frame = cell?.contentView.bounds ?? CGRect.zero
cell?.contentView.addSubview(list.listView())
list.listWillAppear()
if let vc = list as? UIViewController {
vc.beginAppearanceTransition(true, animated: false)
}
}
}
}
private func listDidAppear(at index: Int) {
guard checkIndexValid(index) else {
return
}
currentIndex = index
let list = validListDict[index]
list?.listDidAppear()
if let vc = list as? UIViewController {
vc.endAppearanceTransition()
}
delegate?.listContainerView(self, listDidAppearAt: index)
}
private func listWillDisappear(at index: Int) {
guard checkIndexValid(index) else {
return
}
let list = validListDict[index]
list?.listWillDisappear()
if let vc = list as? UIViewController {
vc.beginAppearanceTransition(false, animated: false)
}
}
private func listDidDisappear(at index: Int) {
guard checkIndexValid(index) else {
return
}
let list = validListDict[index]
list?.listDidDisappear()
if let vc = list as? UIViewController {
vc.endAppearanceTransition()
}
}
private func checkIndexValid(_ index: Int) -> Bool {
guard let dataSource = dataSource else { return false }
let count = dataSource.numberOfLists(in: self)
if count <= 0 || index >= count {
return false
}
return true
}
private func listDidAppearOrDisappear(scrollView: UIScrollView) {
let currentIndexPercent = scrollView.contentOffset.x/scrollView.bounds.size.width
if willAppearIndex != -1 || willDisappearIndex != -1 {
let disappearIndex = willDisappearIndex
let appearIndex = willAppearIndex
if willAppearIndex > willDisappearIndex {
//
if currentIndexPercent >= CGFloat(willAppearIndex) {
willDisappearIndex = -1
willAppearIndex = -1
listDidDisappear(at: disappearIndex)
listDidAppear(at: appearIndex)
}
}else {
//
if currentIndexPercent <= CGFloat(willAppearIndex) {
willDisappearIndex = -1
willAppearIndex = -1
listDidDisappear(at: disappearIndex)
listDidAppear(at: appearIndex)
}
}
}
}
}
extension JXPagingListContainerView: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
guard let dataSource = dataSource else { return 0 }
return dataSource.numberOfLists(in: self)
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.contentView.backgroundColor = listCellBackgroundColor
cell.contentView.subviews.forEach { $0.removeFromSuperview() }
let list = validListDict[indexPath.item]
if list != nil {
if list is UIViewController {
list?.listView().frame = cell.contentView.bounds
}else {
list?.listView().frame = cell.bounds
}
cell.contentView.addSubview(list!.listView())
}
return cell
}
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return bounds.size
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
delegate?.listContainerViewDidScroll(self)
guard scrollView.isTracking || scrollView.isDragging else {
return
}
let percent = scrollView.contentOffset.x/scrollView.bounds.size.width
let maxCount = Int(round(scrollView.contentSize.width/scrollView.bounds.size.width))
var leftIndex = Int(floor(Double(percent)))
leftIndex = max(0, min(maxCount - 1, leftIndex))
let rightIndex = leftIndex + 1;
if percent < 0 || rightIndex >= maxCount {
listDidAppearOrDisappear(scrollView: scrollView)
return
}
let remainderRatio = percent - CGFloat(leftIndex)
if rightIndex == currentIndex {
//
if validListDict[leftIndex] == nil && remainderRatio < (1 - initListPercent) {
initListIfNeeded(at: leftIndex)
}else if validListDict[leftIndex] != nil {
if willAppearIndex == -1 {
willAppearIndex = leftIndex;
listWillAppear(at: willAppearIndex)
}
}
if willDisappearIndex == -1 {
willDisappearIndex = rightIndex
listWillDisappear(at: willDisappearIndex)
}
}else {
//
if validListDict[rightIndex] == nil && remainderRatio > initListPercent {
initListIfNeeded(at: rightIndex)
}else if validListDict[rightIndex] != nil {
if willAppearIndex == -1 {
willAppearIndex = rightIndex
listWillAppear(at: willAppearIndex)
}
}
if willDisappearIndex == -1 {
willDisappearIndex = leftIndex
listWillDisappear(at: willDisappearIndex)
}
}
listDidAppearOrDisappear(scrollView: scrollView)
}
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
//
if willAppearIndex != -1 || willDisappearIndex != -1 {
listWillDisappear(at: willAppearIndex)
listWillAppear(at: willDisappearIndex)
listDidDisappear(at: willAppearIndex)
listDidAppear(at: willDisappearIndex)
willDisappearIndex = -1
willAppearIndex = -1
}
delegate?.listContainerViewDidEndScrolling(self)
}
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
delegate?.listContainerViewWillBeginDragging(self)
}
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if !decelerate {
delegate?.listContainerViewDidEndScrolling(self)
}
}
public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
delegate?.listContainerViewDidEndScrolling(self)
}
}
class JXPagingListContainerViewController: UIViewController {
var viewWillAppearClosure: (()->())?
var viewDidAppearClosure: (()->())?
var viewWillDisappearClosure: (()->())?
var viewDidDisappearClosure: (()->())?
override var shouldAutomaticallyForwardAppearanceMethods: Bool { return false }
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
viewWillAppearClosure?()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
viewDidAppearClosure?()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
viewWillDisappearClosure?()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
viewDidDisappearClosure?()
}
}
class JXPagingListContainerScrollView: UIScrollView, UIGestureRecognizerDelegate {
var isCategoryNestPagingEnabled = false
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if isCategoryNestPagingEnabled, let panGestureClass = NSClassFromString("UIScrollViewPanGestureRecognizer"), gestureRecognizer.isMember(of: panGestureClass) {
let panGesture = gestureRecognizer as! UIPanGestureRecognizer
let velocityX = panGesture.velocity(in: panGesture.view!).x
if velocityX > 0 {
//PagingView
if contentOffset.x == 0 {
return false
}
}else if velocityX < 0 {
//PagingView
if contentOffset.x + bounds.size.width == contentSize.width {
return false
}
}
}
return true
}
}
class JXPagingListContainerCollectionView: UICollectionView, UIGestureRecognizerDelegate {
var isCategoryNestPagingEnabled = false
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if isCategoryNestPagingEnabled, let panGestureClass = NSClassFromString("UIScrollViewPanGestureRecognizer"), gestureRecognizer.isMember(of: panGestureClass) {
let panGesture = gestureRecognizer as! UIPanGestureRecognizer
let velocityX = panGesture.velocity(in: panGesture.view!).x
if velocityX > 0 {
//PagingView
if contentOffset.x == 0 {
return false
}
}else if velocityX < 0 {
//PagingView
if contentOffset.x + bounds.size.width == contentSize.width {
return false
}
}
}
return true
}
}

View File

@ -0,0 +1,89 @@
//
// JXPagingListRefreshView.swift
// JXPagingView
//
// Created by jiaxin on 2018/8/28.
// Copyright © 2018 jiaxin. All rights reserved.
//
import UIKit
open class JXPagingListRefreshView: JXPagingView {
private var lastScrollingListViewContentOffsetY: CGFloat = 0
public override init(delegate: JXPagingViewDelegate, listContainerType: JXPagingListContainerType = .collectionView) {
super.init(delegate: delegate, listContainerType: listContainerType)
mainTableView.bounces = false
}
override open func preferredProcessMainTableViewDidScroll(_ scrollView: UIScrollView) {
if pinSectionHeaderVerticalOffset != 0 {
if !(currentScrollingListView != nil && currentScrollingListView!.contentOffset.y > minContentOffsetYInListScrollView(currentScrollingListView!)) {
//listView
if scrollView.contentOffset.y <= 0 {
mainTableView.bounces = false
mainTableView.contentOffset = CGPoint.zero
return
}else {
mainTableView.bounces = true
}
}
}
guard let currentScrollingListView = currentScrollingListView else { return }
if (currentScrollingListView.contentOffset.y > minContentOffsetYInListScrollView(currentScrollingListView)) {
//mainTableViewheaderlistViewmainTableViewcontentOffset
setMainTableViewToMaxContentOffsetY()
}
if (mainTableView.contentOffset.y < mainTableViewMaxContentOffsetY()) {
//mainTableViewheaderlistViewcontentOffset
for list in validListDict.values {
//
if list.listScrollView().contentOffset.y > minContentOffsetYInListScrollView(list.listScrollView()) {
setListScrollViewToMinContentOffsetY(list.listScrollView())
}
}
}
if scrollView.contentOffset.y > mainTableViewMaxContentOffsetY() && currentScrollingListView.contentOffset.y == minContentOffsetYInListScrollView(currentScrollingListView) {
//mainTableViewheaderViewlistView
setMainTableViewToMaxContentOffsetY()
}
}
override open func preferredProcessListViewDidScroll(scrollView: UIScrollView) {
guard let currentScrollingListView = currentScrollingListView else { return }
var shouldProcess = true
if currentScrollingListView.contentOffset.y > lastScrollingListViewContentOffsetY {
//
}else {
//
if mainTableView.contentOffset.y == 0 {
shouldProcess = false
}else {
if (mainTableView.contentOffset.y < mainTableViewMaxContentOffsetY()) {
//mainTableViewheaderlistScrollView0
setListScrollViewToMinContentOffsetY(currentScrollingListView)
currentScrollingListView.showsVerticalScrollIndicator = false;
}
}
}
if shouldProcess {
if (mainTableView.contentOffset.y < mainTableViewMaxContentOffsetY()) {
//scrollView.contentOffset.y0
if currentScrollingListView.contentOffset.y > minContentOffsetYInListScrollView(currentScrollingListView) {
//mainTableViewheaderlistScrollView0
setListScrollViewToMinContentOffsetY(currentScrollingListView)
currentScrollingListView.showsVerticalScrollIndicator = false;
}
} else {
//mainTableViewheadermainTableViewlistScrollView
setMainTableViewToMaxContentOffsetY()
currentScrollingListView.showsVerticalScrollIndicator = true;
}
}
lastScrollingListViewContentOffsetY = currentScrollingListView.contentOffset.y;
}
}

View File

@ -0,0 +1,26 @@
//
// JXPagingViewMainTableView.swift
// JXPagingView
//
// Created by jiaxin on 2018/5/22.
// Copyright © 2018 jiaxin. All rights reserved.
//
import UIKit
@objc public protocol JXPagingMainTableViewGestureDelegate {
//headerViewscrollView
func mainTableViewGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool
}
open class JXPagingMainTableView: UITableView, UIGestureRecognizerDelegate {
public weak var gestureDelegate: JXPagingMainTableViewGestureDelegate?
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureDelegate != nil {
return gestureDelegate!.mainTableViewGestureRecognizer(gestureRecognizer, shouldRecognizeSimultaneouslyWith:otherGestureRecognizer)
}else {
return gestureRecognizer.isKind(of: UIPanGestureRecognizer.self) && otherGestureRecognizer.isKind(of: UIPanGestureRecognizer.self)
}
}
}

View File

@ -0,0 +1,366 @@
//
// JXPagingSmoothView.swift
// JXPagingView
//
// Created by jiaxin on 2019/11/20.
// Copyright © 2019 jiaxin. All rights reserved.
//
import UIKit
@objc public protocol JXPagingSmoothViewListViewDelegate {
/// listViewvcvc.viewviewview
func listView() -> UIView
/// JXPagerSmoothViewListViewDelegateUIScrollViewUITableViewUICollectionView
func listScrollView() -> UIScrollView
@objc optional func listDidAppear()
@objc optional func listDidDisappear()
}
@objc
public protocol JXPagingSmoothViewDataSource {
/// header
func heightForPagingHeader(in pagingView: JXPagingSmoothView) -> CGFloat
/// header
func viewForPagingHeader(in pagingView: JXPagingSmoothView) -> UIView
///
func heightForPinHeader(in pagingView: JXPagingSmoothView) -> CGFloat
///
func viewForPinHeader(in pagingView: JXPagingSmoothView) -> UIView
///
func numberOfLists(in pagingView: JXPagingSmoothView) -> Int
/// index`JXPagingSmoothViewListViewDelegate`
/// UIViewUIView`JXPagingSmoothViewListViewDelegate`UIView
/// UIViewControllerUIViewController`JXPagingSmoothViewListViewDelegate`UIViewController
func pagingView(_ pagingView: JXPagingSmoothView, initListAtIndex index: Int) -> JXPagingSmoothViewListViewDelegate
}
@objc
public protocol JXPagingSmoothViewDelegate {
@objc optional func pagingSmoothViewDidScroll(_ scrollView: UIScrollView)
}
open class JXPagingSmoothView: UIView {
public private(set) var listDict = [Int : JXPagingSmoothViewListViewDelegate]()
public let listCollectionView: JXPagingSmoothCollectionView
public var defaultSelectedIndex: Int = 0
public weak var delegate: JXPagingSmoothViewDelegate?
weak var dataSource: JXPagingSmoothViewDataSource?
var listHeaderDict = [Int : UIView]()
var isSyncListContentOffsetEnabled: Bool = false
let pagingHeaderContainerView: UIView
var currentPagingHeaderContainerViewY: CGFloat = 0
var currentIndex: Int = 0
var currentListScrollView: UIScrollView?
var heightForPagingHeader: CGFloat = 0
var heightForPinHeader: CGFloat = 0
var heightForPagingHeaderContainerView: CGFloat = 0
let cellIdentifier = "cell"
var currentListInitializeContentOffsetY: CGFloat = 0
var singleScrollView: UIScrollView?
deinit {
listDict.values.forEach {
$0.listScrollView().removeObserver(self, forKeyPath: "contentOffset")
$0.listScrollView().removeObserver(self, forKeyPath: "contentSize")
}
}
public init(dataSource: JXPagingSmoothViewDataSource) {
self.dataSource = dataSource
pagingHeaderContainerView = UIView()
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
layout.scrollDirection = .horizontal
listCollectionView = JXPagingSmoothCollectionView(frame: CGRect.zero, collectionViewLayout: layout)
super.init(frame: CGRect.zero)
listCollectionView.dataSource = self
listCollectionView.delegate = self
listCollectionView.isPagingEnabled = true
listCollectionView.bounces = false
listCollectionView.showsHorizontalScrollIndicator = false
listCollectionView.scrollsToTop = false
listCollectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellIdentifier)
if #available(iOS 10.0, *) {
listCollectionView.isPrefetchingEnabled = false
}
if #available(iOS 11.0, *) {
listCollectionView.contentInsetAdjustmentBehavior = .never
}
listCollectionView.pagingHeaderContainerView = pagingHeaderContainerView
addSubview(listCollectionView)
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public func reloadData() {
guard let dataSource = dataSource else { return }
currentListScrollView = nil
currentIndex = defaultSelectedIndex
currentPagingHeaderContainerViewY = 0
isSyncListContentOffsetEnabled = false
listHeaderDict.removeAll()
listDict.values.forEach { (list) in
list.listScrollView().removeObserver(self, forKeyPath: "contentOffset")
list.listScrollView().removeObserver(self, forKeyPath: "contentSize")
list.listView().removeFromSuperview()
}
listDict.removeAll()
heightForPagingHeader = dataSource.heightForPagingHeader(in: self)
heightForPinHeader = dataSource.heightForPinHeader(in: self)
heightForPagingHeaderContainerView = heightForPagingHeader + heightForPinHeader
let pagingHeader = dataSource.viewForPagingHeader(in: self)
let pinHeader = dataSource.viewForPinHeader(in: self)
pagingHeaderContainerView.addSubview(pagingHeader)
pagingHeaderContainerView.addSubview(pinHeader)
pagingHeaderContainerView.frame = CGRect(x: 0, y: 0, width: bounds.size.width, height: heightForPagingHeaderContainerView)
pagingHeader.frame = CGRect(x: 0, y: 0, width: bounds.size.width, height: heightForPagingHeader)
pinHeader.frame = CGRect(x: 0, y: heightForPagingHeader, width: bounds.size.width, height: heightForPinHeader)
listCollectionView.setContentOffset(CGPoint(x: listCollectionView.bounds.size.width*CGFloat(defaultSelectedIndex), y: 0), animated: false)
listCollectionView.reloadData()
if dataSource.numberOfLists(in: self) == 0 {
singleScrollView = UIScrollView()
addSubview(singleScrollView!)
singleScrollView?.addSubview(pagingHeader)
singleScrollView?.contentSize = CGSize(width: bounds.size.width, height: heightForPagingHeader)
}else if singleScrollView != nil {
singleScrollView?.removeFromSuperview()
singleScrollView = nil
}
}
open override func layoutSubviews() {
super.layoutSubviews()
listCollectionView.frame = bounds
if pagingHeaderContainerView.frame == CGRect.zero {
reloadData()
}
if singleScrollView != nil {
singleScrollView?.frame = bounds
}
}
func listDidScroll(scrollView: UIScrollView) {
if listCollectionView.isDragging || listCollectionView.isDecelerating {
return
}
let index = listIndex(for: scrollView)
if index != currentIndex {
return
}
currentListScrollView = scrollView
let contentOffsetY = scrollView.contentOffset.y + heightForPagingHeaderContainerView
if contentOffsetY < heightForPagingHeader {
isSyncListContentOffsetEnabled = true
currentPagingHeaderContainerViewY = -contentOffsetY
for list in listDict.values {
if list.listScrollView() != currentListScrollView {
list.listScrollView().setContentOffset(scrollView.contentOffset, animated: false)
}
}
let header = listHeader(for: scrollView)
if pagingHeaderContainerView.superview != header {
pagingHeaderContainerView.frame.origin.y = 0
header?.addSubview(pagingHeaderContainerView)
}
}else {
if pagingHeaderContainerView.superview != self {
pagingHeaderContainerView.frame.origin.y = -heightForPagingHeader
addSubview(pagingHeaderContainerView)
}
if isSyncListContentOffsetEnabled {
isSyncListContentOffsetEnabled = false
currentPagingHeaderContainerViewY = -heightForPagingHeader
for list in listDict.values {
if list.listScrollView() != currentListScrollView {
list.listScrollView().setContentOffset(CGPoint(x: 0, y: -heightForPinHeader), animated: false)
}
}
}
}
}
//MARK: - KVO
open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "contentOffset" {
if let scrollView = object as? UIScrollView {
listDidScroll(scrollView: scrollView)
}
}else if keyPath == "contentSize" {
if let scrollView = object as? UIScrollView {
let minContentSizeHeight = bounds.size.height - heightForPinHeader
if minContentSizeHeight > scrollView.contentSize.height {
scrollView.contentSize = CGSize(width: scrollView.contentSize.width, height: minContentSizeHeight)
//scrollViewcontentOffset
if currentListScrollView != nil, scrollView != currentListScrollView! {
scrollView.contentOffset = CGPoint(x: 0, y: currentListInitializeContentOffsetY)
}
}
}
}else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
//MARK: - Private
func listHeader(for listScrollView: UIScrollView) -> UIView? {
for (index, list) in listDict {
if list.listScrollView() == listScrollView {
return listHeaderDict[index]
}
}
return nil
}
func listIndex(for listScrollView: UIScrollView) -> Int {
for (index, list) in listDict {
if list.listScrollView() == listScrollView {
return index
}
}
return 0
}
func listDidAppear(at index: Int) {
guard let dataSource = dataSource else { return }
let count = dataSource.numberOfLists(in: self)
if count <= 0 || index >= count {
return
}
listDict[index]?.listDidAppear?()
}
func listDidDisappear(at index: Int) {
guard let dataSource = dataSource else { return }
let count = dataSource.numberOfLists(in: self)
if count <= 0 || index >= count {
return
}
listDict[index]?.listDidDisappear?()
}
/// pagerHeaderContainerViewindex
func horizontalScrollDidEnd(at index: Int) {
currentIndex = index
guard let listHeader = listHeaderDict[index], let listScrollView = listDict[index]?.listScrollView() else {
return
}
listDict.values.forEach { $0.listScrollView().scrollsToTop = ($0.listScrollView() === listScrollView) }
if listScrollView.contentOffset.y <= -heightForPinHeader {
pagingHeaderContainerView.frame.origin.y = 0
listHeader.addSubview(pagingHeaderContainerView)
}
}
}
extension JXPagingSmoothView: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return bounds.size
}
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
guard let dataSource = dataSource else { return 0 }
return dataSource.numberOfLists(in: self)
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let dataSource = dataSource else { return UICollectionViewCell(frame: CGRect.zero) }
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath)
var list = listDict[indexPath.item]
if list == nil {
list = dataSource.pagingView(self, initListAtIndex: indexPath.item)
listDict[indexPath.item] = list!
list?.listView().setNeedsLayout()
list?.listView().layoutIfNeeded()
if list?.listScrollView().isKind(of: UITableView.self) == true {
(list?.listScrollView() as? UITableView)?.estimatedRowHeight = 0
(list?.listScrollView() as? UITableView)?.estimatedSectionHeaderHeight = 0
(list?.listScrollView() as? UITableView)?.estimatedSectionFooterHeight = 0
}
if #available(iOS 11.0, *) {
list?.listScrollView().contentInsetAdjustmentBehavior = .never
}
list?.listScrollView().contentInset = UIEdgeInsets(top: heightForPagingHeaderContainerView, left: 0, bottom: 0, right: 0)
currentListInitializeContentOffsetY = -heightForPagingHeaderContainerView + min(-currentPagingHeaderContainerViewY, heightForPagingHeader)
list?.listScrollView().contentOffset = CGPoint(x: 0, y: currentListInitializeContentOffsetY)
let listHeader = UIView(frame: CGRect(x: 0, y: -heightForPagingHeaderContainerView, width: bounds.size.width, height: heightForPagingHeaderContainerView))
list?.listScrollView().addSubview(listHeader)
if pagingHeaderContainerView.superview == nil {
listHeader.addSubview(pagingHeaderContainerView)
}
listHeaderDict[indexPath.item] = listHeader
list?.listScrollView().addObserver(self, forKeyPath: "contentOffset", options: .new, context: nil)
list?.listScrollView().addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
}
listDict.values.forEach { $0.listScrollView().scrollsToTop = ($0 === list) }
if let listView = list?.listView(), listView.superview != cell.contentView {
cell.contentView.subviews.forEach { $0.removeFromSuperview() }
listView.frame = cell.contentView.bounds
cell.contentView.addSubview(listView)
}
return cell
}
public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
listDidAppear(at: indexPath.item)
}
public func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
listDidDisappear(at: indexPath.item)
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
delegate?.pagingSmoothViewDidScroll?(scrollView)
let indexPercent = scrollView.contentOffset.x/scrollView.bounds.size.width
let index = Int(scrollView.contentOffset.x/scrollView.bounds.size.width)
let listScrollView = listDict[index]?.listScrollView()
if (indexPercent - CGFloat(index) == 0) && index != currentIndex && !(scrollView.isDragging || scrollView.isDecelerating) && listScrollView?.contentOffset.y ?? 0 <= -heightForPinHeader {
horizontalScrollDidEnd(at: index)
}else {
//listHeaderContainerViewself
if pagingHeaderContainerView.superview != self {
pagingHeaderContainerView.frame.origin.y = currentPagingHeaderContainerViewY
addSubview(pagingHeaderContainerView)
}
}
if index != currentIndex {
currentIndex = index
}
}
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if !decelerate {
let index = Int(scrollView.contentOffset.x/scrollView.bounds.size.width)
horizontalScrollDidEnd(at: index)
}
}
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let index = Int(scrollView.contentOffset.x/scrollView.bounds.size.width)
horizontalScrollDidEnd(at: index)
}
}
public class JXPagingSmoothCollectionView: UICollectionView, UIGestureRecognizerDelegate {
var pagingHeaderContainerView: UIView?
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
let point = touch.location(in: pagingHeaderContainerView)
if pagingHeaderContainerView?.bounds.contains(point) == true {
return false
}
return true
}
}

View File

@ -0,0 +1,446 @@
//
// JXPagingView.swift
// JXPagingView
//
// Created by jiaxin on 2018/5/22.
// Copyright © 2018 jiaxin. All rights reserved.
//
import UIKit
public protocol JXPagingViewDelegate: NSObjectProtocol {
/// tableHeaderView
func tableHeaderViewHeight(in pagingView: JXPagingView) -> Int
/// tableHeaderView
func tableHeaderView(in pagingView: JXPagingView) -> UIView
/// HeaderView
func heightForPinSectionHeader(in pagingView: JXPagingView) -> Int
/// HeaderView
func viewForPinSectionHeader(in pagingView: JXPagingView) -> UIView
///
func numberOfLists(in pagingView: JXPagingView) -> Int
/// index`JXPagerViewListViewDelegate`
/// UIViewUIView`JXPagerViewListViewDelegate`UIView
/// UIViewControllerUIViewController`JXPagerViewListViewDelegate`UIViewController
///
/// - Parameters:
/// - pagingView: pagingView description
/// - index:
func pagingView(_ pagingView: JXPagingView, initListAtIndex index: Int) -> JXPagingViewListViewDelegate
/// index
/// - Parameters:
/// - pagingView: pagingView description
/// - index:
func pagingView(_ pagingView: JXPagingView, listIdentifierAtIndex index: Int) -> String?
/// 使pagingView(_ pagingView: JXPagingView, mainTableViewDidScroll scrollView: UIScrollView)
@available(*, message: "Use pagingView(_ pagingView: JXPagingView, mainTableViewDidScroll scrollView: UIScrollView) method")
func mainTableViewDidScroll(_ scrollView: UIScrollView)
func pagingView(_ pagingView: JXPagingView, mainTableViewDidScroll scrollView: UIScrollView)
func pagingView(_ pagingView: JXPagingView, mainTableViewWillBeginDragging scrollView: UIScrollView)
func pagingView(_ pagingView: JXPagingView, mainTableViewDidEndDragging scrollView: UIScrollView, willDecelerate decelerate: Bool)
func pagingView(_ pagingView: JXPagingView, mainTableViewDidEndDecelerating scrollView: UIScrollView)
func pagingView(_ pagingView: JXPagingView, mainTableViewDidEndScrollingAnimation scrollView: UIScrollView)
/// UIScrollViewUICollectionViewClass
/// UIScrollViewFDFullscreenPopGesture
///
/// - Parameter pagingView: JXPagingView
/// - Returns: UIScrollView
func scrollViewClassInListContainerView(in pagingView: JXPagingView) -> AnyClass?
}
public extension JXPagingViewDelegate {
func pagingView(_ pagingView: JXPagingView, listIdentifierAtIndex index: Int) -> String? { nil }
func mainTableViewDidScroll(_ scrollView: UIScrollView) {}
func pagingView(_ pagingView: JXPagingView, mainTableViewDidScroll scrollView: UIScrollView) {}
func pagingView(_ pagingView: JXPagingView, mainTableViewWillBeginDragging scrollView: UIScrollView) {}
func pagingView(_ pagingView: JXPagingView, mainTableViewDidEndDragging scrollView: UIScrollView, willDecelerate decelerate: Bool) {}
func pagingView(_ pagingView: JXPagingView, mainTableViewDidEndDecelerating scrollView: UIScrollView) {}
func pagingView(_ pagingView: JXPagingView, mainTableViewDidEndScrollingAnimation scrollView: UIScrollView) {}
/// UIScrollViewUICollectionViewClass
/// UIScrollViewFDFullscreenPopGesture
///
/// - Parameter pagingView: JXPagingView
/// - Returns: UIScrollView
func scrollViewClassInListContainerView(in pagingView: JXPagingView) -> AnyClass? { nil }
}
open class JXPagingView: UIView {
/// categoryView.defaultSelectedIndex
public var defaultSelectedIndex: Int = 0 {
didSet {
listContainerView.defaultSelectedIndex = defaultSelectedIndex
}
}
public private(set) lazy var mainTableView: JXPagingMainTableView = JXPagingMainTableView(frame: CGRect.zero, style: .plain)
public private(set) lazy var listContainerView: JXPagingListContainerView = JXPagingListContainerView(dataSource: self, type: listContainerType)
/// keyindexvalue
public private(set) var validListDict = [Int:JXPagingViewListViewDelegate]()
/// sectionHeader
public var pinSectionHeaderVerticalOffset: Int = 0
public var isListHorizontalScrollEnabled = true {
didSet {
listContainerView.scrollView.isScrollEnabled = isListHorizontalScrollEnabled
}
}
/// trueheaderViewfalsetrue
public var automaticallyDisplayListVerticalScrollIndicator = true
/// allowsCacheListtrue`func pagingView(_ pagingView: JXPagingView, listIdentifierAtIndex index: Int) -> String`
public var allowsCacheList: Bool = false
public private(set) var currentScrollingListView: UIScrollView?
internal var currentList: JXPagingViewListViewDelegate?
private var currentIndex: Int = 0
private weak var delegate: JXPagingViewDelegate?
private var tableHeaderContainerView: UIView!
private let cellIdentifier = "cell"
private let listContainerType: JXPagingListContainerType
private var listCache = [String:JXPagingViewListViewDelegate]()
public init(delegate: JXPagingViewDelegate, listContainerType: JXPagingListContainerType = .collectionView) {
self.delegate = delegate
self.listContainerType = listContainerType
super.init(frame: CGRect.zero)
listContainerView.delegate = self
mainTableView.showsVerticalScrollIndicator = false
mainTableView.showsHorizontalScrollIndicator = false
mainTableView.separatorStyle = .none
mainTableView.dataSource = self
mainTableView.delegate = self
mainTableView.scrollsToTop = false
refreshTableHeaderView()
mainTableView.register(UITableViewCell.self, forCellReuseIdentifier: cellIdentifier)
if #available(iOS 11.0, *) {
mainTableView.contentInsetAdjustmentBehavior = .never
}
if #available(iOS 15.0, *) {
mainTableView.sectionHeaderTopPadding = 0
}
addSubview(mainTableView)
}
@available(*, unavailable)
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override open func layoutSubviews() {
super.layoutSubviews()
if mainTableView.frame != bounds {
mainTableView.frame = bounds
mainTableView.reloadData()
}
}
open func reloadData() {
currentList = nil
currentScrollingListView = nil
validListDict.removeAll()
if allowsCacheList, let listCount = delegate?.numberOfLists(in: self) {
//list
var newListIdentifierArray = [String]()
for index in 0..<listCount {
if let listIdentifier = delegate?.pagingView(self, listIdentifierAtIndex: index) {
newListIdentifierArray.append(listIdentifier)
}
}
let existedKeys = Array(listCache.keys)
for listIdentifier in existedKeys {
if !newListIdentifierArray.contains(listIdentifier) {
listCache.removeValue(forKey: listIdentifier)
}
}
}
refreshTableHeaderView()
if pinSectionHeaderVerticalOffset != 0 && mainTableView.contentOffset.y > CGFloat(pinSectionHeaderVerticalOffset) {
mainTableView.contentOffset = .zero
}
mainTableView.reloadData()
listContainerView.reloadData()
}
open func resizeTableHeaderViewHeight(animatable: Bool = false, duration: TimeInterval = 0.25, curve: UIView.AnimationCurve = .linear) {
guard let delegate = delegate else { return }
if animatable {
var options: UIView.AnimationOptions = .curveLinear
switch curve {
case .easeIn: options = .curveEaseIn
case .easeOut: options = .curveEaseOut
case .easeInOut: options = .curveEaseInOut
default: break
}
var bounds = tableHeaderContainerView.bounds
bounds.size.height = CGFloat(delegate.tableHeaderViewHeight(in: self))
UIView.animate(withDuration: duration, delay: 0, options: options, animations: {
self.tableHeaderContainerView.frame = bounds
self.mainTableView.tableHeaderView = self.tableHeaderContainerView
self.mainTableView.setNeedsLayout()
self.mainTableView.layoutIfNeeded()
}, completion: nil)
}else {
var bounds = tableHeaderContainerView.bounds
bounds.size.height = CGFloat(delegate.tableHeaderViewHeight(in: self))
tableHeaderContainerView.frame = bounds
mainTableView.tableHeaderView = tableHeaderContainerView
}
}
open func preferredProcessListViewDidScroll(scrollView: UIScrollView) {
if (mainTableView.contentOffset.y < mainTableViewMaxContentOffsetY()) {
//mainTableViewheaderlistScrollView0
currentList?.listScrollViewWillResetContentOffset()
setListScrollViewToMinContentOffsetY(scrollView)
if automaticallyDisplayListVerticalScrollIndicator {
scrollView.showsVerticalScrollIndicator = false
}
} else {
//mainTableViewheadermainTableViewlistScrollView
setMainTableViewToMaxContentOffsetY()
if automaticallyDisplayListVerticalScrollIndicator {
scrollView.showsVerticalScrollIndicator = true
}
}
}
open func preferredProcessMainTableViewDidScroll(_ scrollView: UIScrollView) {
guard let currentScrollingListView = currentScrollingListView else { return }
if (currentScrollingListView.contentOffset.y > minContentOffsetYInListScrollView(currentScrollingListView)) {
//mainTableViewheaderlistViewmainTableViewcontentOffset
setMainTableViewToMaxContentOffsetY()
}
if (mainTableView.contentOffset.y < mainTableViewMaxContentOffsetY()) {
//mainTableViewheaderlistViewcontentOffset
for list in validListDict.values {
list.listScrollViewWillResetContentOffset()
setListScrollViewToMinContentOffsetY(list.listScrollView())
}
}
if scrollView.contentOffset.y > mainTableViewMaxContentOffsetY() && currentScrollingListView.contentOffset.y == minContentOffsetYInListScrollView(currentScrollingListView) {
//mainTableViewheaderViewlistView
setMainTableViewToMaxContentOffsetY()
}
}
//MARK: - Private
func refreshTableHeaderView() {
guard let delegate = delegate else { return }
let tableHeaderView = delegate.tableHeaderView(in: self)
let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: CGFloat(delegate.tableHeaderViewHeight(in: self))))
containerView.addSubview(tableHeaderView)
tableHeaderView.translatesAutoresizingMaskIntoConstraints = false
let top = NSLayoutConstraint(item: tableHeaderView, attribute: .top, relatedBy: .equal, toItem: containerView, attribute: .top, multiplier: 1, constant: 0)
let leading = NSLayoutConstraint(item: tableHeaderView, attribute: .leading, relatedBy: .equal, toItem: containerView, attribute: .leading, multiplier: 1, constant: 0)
let bottom = NSLayoutConstraint(item: tableHeaderView, attribute: .bottom, relatedBy: .equal, toItem: containerView, attribute: .bottom, multiplier: 1, constant: 0)
let trailing = NSLayoutConstraint(item: tableHeaderView, attribute: .trailing, relatedBy: .equal, toItem: containerView, attribute: .trailing, multiplier: 1, constant: 0)
containerView.addConstraints([top, leading, bottom, trailing])
tableHeaderContainerView = containerView
mainTableView.tableHeaderView = containerView
}
func adjustMainScrollViewToTargetContentInsetIfNeeded(inset: UIEdgeInsets) {
if mainTableView.contentInset != inset {
//
mainTableView.delegate = nil
mainTableView.contentInset = inset
mainTableView.delegate = self
}
}
//pinSectionHeaderVerticalOffsetMJRefreshJXPagingViewMJRefreshcontentInsetpinSectionHeaderVerticalOffsetcontentInset.top
//https://github.com/pujiaxin33/JXPagingView/issues/203
func isSetMainScrollViewContentInsetToZeroEnabled(scrollView: UIScrollView) -> Bool {
return !(scrollView.contentInset.top != 0 && scrollView.contentInset.top != CGFloat(pinSectionHeaderVerticalOffset))
}
func mainTableViewMaxContentOffsetY() -> CGFloat {
guard let delegate = delegate else { return 0 }
return CGFloat(delegate.tableHeaderViewHeight(in: self)) - CGFloat(pinSectionHeaderVerticalOffset)
}
func setMainTableViewToMaxContentOffsetY() {
mainTableView.contentOffset = CGPoint(x: 0, y: mainTableViewMaxContentOffsetY())
}
func minContentOffsetYInListScrollView(_ scrollView: UIScrollView) -> CGFloat {
if #available(iOS 11.0, *) {
return -scrollView.adjustedContentInset.top
}
return -scrollView.contentInset.top
}
func setListScrollViewToMinContentOffsetY(_ scrollView: UIScrollView) {
scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x, y: minContentOffsetYInListScrollView(scrollView))
}
func pinSectionHeaderHeight() -> CGFloat {
guard let delegate = delegate else { return 0 }
return CGFloat(delegate.heightForPinSectionHeader(in: self))
}
/// listViewscrollView
func listViewDidScroll(scrollView: UIScrollView) {
currentScrollingListView = scrollView
preferredProcessListViewDidScroll(scrollView: scrollView)
}
}
//MARK: - UITableViewDataSource, UITableViewDelegate
extension JXPagingView: UITableViewDataSource, UITableViewDelegate {
open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
open func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return max(bounds.height - pinSectionHeaderHeight() - CGFloat(pinSectionHeaderVerticalOffset), 0)
}
open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
cell.selectionStyle = .none
cell.backgroundColor = UIColor.clear
if listContainerView.superview != cell.contentView {
cell.contentView.addSubview(listContainerView)
}
if listContainerView.frame != cell.bounds {
listContainerView.frame = cell.bounds
}
return cell
}
open func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return pinSectionHeaderHeight()
}
open func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard let delegate = delegate else { return nil }
return delegate.viewForPinSectionHeader(in: self)
}
//footer
open func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 1
}
open func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let footerView = UIView(frame: CGRect.zero)
footerView.backgroundColor = UIColor.clear
return footerView
}
open func scrollViewDidScroll(_ scrollView: UIScrollView) {
if pinSectionHeaderVerticalOffset != 0 {
if !(currentScrollingListView != nil && currentScrollingListView!.contentOffset.y > minContentOffsetYInListScrollView(currentScrollingListView!)) {
//listView
if scrollView.contentOffset.y >= CGFloat(pinSectionHeaderVerticalOffset) {
//contentInset.top
adjustMainScrollViewToTargetContentInsetIfNeeded(inset: UIEdgeInsets(top: CGFloat(pinSectionHeaderVerticalOffset), left: 0, bottom: 0, right: 0))
}else {
if isSetMainScrollViewContentInsetToZeroEnabled(scrollView: scrollView) {
adjustMainScrollViewToTargetContentInsetIfNeeded(inset: UIEdgeInsets.zero)
}
}
}
}
preferredProcessMainTableViewDidScroll(scrollView)
delegate?.mainTableViewDidScroll(scrollView)
delegate?.pagingView(self, mainTableViewDidScroll: scrollView)
}
open func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
//
listContainerView.scrollView.isScrollEnabled = false
delegate?.pagingView(self, mainTableViewWillBeginDragging: scrollView)
}
open func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if isListHorizontalScrollEnabled && !decelerate {
listContainerView.scrollView.isScrollEnabled = true
}
delegate?.pagingView(self, mainTableViewDidEndDragging: scrollView, willDecelerate: decelerate)
}
open func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if isListHorizontalScrollEnabled {
listContainerView.scrollView.isScrollEnabled = true
}
if isSetMainScrollViewContentInsetToZeroEnabled(scrollView: scrollView) {
if mainTableView.contentInset.top != 0 && pinSectionHeaderVerticalOffset != 0 {
adjustMainScrollViewToTargetContentInsetIfNeeded(inset: UIEdgeInsets.zero)
}
}
delegate?.pagingView(self, mainTableViewDidEndDecelerating: scrollView)
}
open func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
if isListHorizontalScrollEnabled {
listContainerView.scrollView.isScrollEnabled = true
}
delegate?.pagingView(self, mainTableViewDidEndScrollingAnimation: scrollView)
}
}
extension JXPagingView: JXPagingListContainerViewDataSource {
public func numberOfLists(in listContainerView: JXPagingListContainerView) -> Int {
guard let delegate = delegate else { return 0 }
return delegate.numberOfLists(in: self)
}
public func listContainerView(_ listContainerView: JXPagingListContainerView, initListAt index: Int) -> JXPagingViewListViewDelegate {
guard let delegate = delegate else { fatalError("JXPaingView.delegate must not be nil") }
var list = validListDict[index]
if allowsCacheList, list == nil, let listIdentifier = delegate.pagingView(self, listIdentifierAtIndex: index) {
list = listCache[listIdentifier]
}
if list == nil {
list = delegate.pagingView(self, initListAtIndex: index)
list?.listViewDidScrollCallback {[weak self, weak list] (scrollView) in
self?.currentList = list
self?.listViewDidScroll(scrollView: scrollView)
}
validListDict[index] = list!
if allowsCacheList, let listIdentifier = delegate.pagingView(self, listIdentifierAtIndex: index) {
listCache[listIdentifier] = list
}
}
return list!
}
public func scrollViewClass(in listContainerView: JXPagingListContainerView) -> AnyClass? {
return delegate?.scrollViewClassInListContainerView(in: self) ?? UIView.self
}
}
extension JXPagingView: JXPagingListContainerViewDelegate {
public func listContainerViewWillBeginDragging(_ listContainerView: JXPagingListContainerView) {
mainTableView.isScrollEnabled = false
}
public func listContainerViewDidEndScrolling(_ listContainerView: JXPagingListContainerView) {
mainTableView.isScrollEnabled = true
}
public func listContainerView(_ listContainerView: JXPagingListContainerView, listDidAppearAt index: Int) {
currentScrollingListView = validListDict[index]?.listScrollView()
for listItem in validListDict.values {
if listItem === validListDict[index] {
listItem.listScrollView().scrollsToTop = true
}else {
listItem.listScrollView().scrollsToTop = false
}
}
}
}

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>

21
Pods/JXSegmentedView/LICENSE generated Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 暴走的鑫鑫
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.

269
Pods/JXSegmentedView/README-English.md generated Normal file
View File

@ -0,0 +1,269 @@
<div align=center><img src="Example/JXSegmentedViewExample/Image/JXSegmentedViewSmall.png" width="467" height="84" /></div>
[![platform](https://img.shields.io/badge/platform-iOS-blue.svg?style=plastic)](#)
[![languages](https://img.shields.io/badge/language-swift-blue.svg)](#)
[![cocoapods](https://img.shields.io/badge/cocoapods-supported-4BC51D.svg?style=plastic)](https://cocoapods.org/pods/JXSegmentedView)
[![support](https://img.shields.io/badge/support-ios%208%2B-orange.svg)](#)
A powerful and easy to use segmented view (segmentedcontrol, pagingview, pagerview, pagecontrol, categoryview)
Advantages compared to other similar tripartite libraries:
- Indicator logic use Protocol Oriented Programming, which can be easily to extension;
- Provide more comprehensive and rich effects, and support almost all popular APP effects;
- Use subclassing to manage cell styles, with clearer logic and simpler extensions;
## Objective-C Version
If you are looking for the Objective-C version, please click to view
[JXCategoryView](https://github.com/pujiaxin33/JXCategoryView)
## Preview
### Indicator Preview
Description | Gif |
----|------|
Line fixed width | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Indicator/LineFixedWidth.gif" width="350" height="80"> |
Line flexible width | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Indicator/LineFlexibleWidth.gif" width="350" height="80"> |
Line lengthen | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Indicator/LineLengthen.gif" width="350" height="80"> |
Line lengthen and offset | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Indicator/LineLengthenOffset.gif" width="350" height="80"> |
RainbowLine | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Indicator/LineRainbow.gif" width="350" height="80"> |
DotLine | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Indicator/LineDot.gif" width="334" height="80"> |
DoubleLine | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Indicator/LineDouble.gif" width="350" height="80"> |
Triangle bottom | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Indicator/Triangle.gif" width="350" height="80"> |
Triangle top | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Indicator/TriangleTop.gif" width="350" height="80"> |
Background | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Indicator/IndicatorBackground.gif" width="350" height="80"> |
Background with shadow | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Indicator/IndicatorBackgroundShadow.gif" width="350" height="80"> |
Background mask | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Indicator/IndicatorBackgroundMask.gif" width="350" height="80"> |
Background mask without bottom view | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Indicator/IndicatorBackgroundMaskPure.gif" width="350" height="80"> |
Background gradient<br>(fixed) | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Indicator/IndicatorBackgroundGradient.gif" width="350" height="80"> |
Gradient<br>(change with position) | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Indicator/IndicatorGradient.gif" width="350" height="80"> |
Image bottom | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Indicator/IndicatorImageBottom.gif" width="350" height="80"> |
Image background | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Indicator/IndicatorImageBG.gif" width="350" height="80"> |
mixed indicators | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Indicator/IndicatorMixed.gif" width="350" height="80"> |
The following indicators support up and down position switching
`JXSegmentedIndicatorLineView`、`JXSegmentedIndicatorRainbowLineView`、`JXSegmentedIndicatorDotLineView`、`JXSegmentedIndicatorDoubleLineView`、`JXSegmentedIndicatorTriangleView`、`JXSegmentedIndicatorImageView`
### Cell Preview
Description | Gif |
----|------|
title color gradient | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Cell/ColorGradient.gif" width="350" height="80"> |
text color gradient | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Cell/TextGradient.gif" width="350" height="80"> |
transform zoom | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Cell/ZoomOnly.gif" width="350" height="80"> |
transform zoom + stroke width | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Cell/ZoomStrokeWidth.gif" width="350" height="80"> |
transform zoom + selected animation | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Cell/ZoomAnimation.gif" width="350" height="80"> |
transform zoom + cell width zoom | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Cell/ZoomCellWidth.gif" width="350" height="80"> |
TitleImage_Top | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Cell/TitleImageTop.gif" width="350" height="80"> |
TitleImage_Left | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Cell/TitleImageLeft.gif" width="350" height="80"> |
TitleImage_Bottom | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Cell/TitleImageBottom.gif" width="350" height="80"> |
TitleImage_Right | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Cell/TitleImageRight.gif" width="350" height="80"> |
TitleImage_OnlyImage | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Cell/TitleImageOnlyImage.gif" width="350" height="80"> |
TitleOrImage | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Cell/TitleOrImage.gif" width="350" height="80"> |
number | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Cell/Number.gif" width="350" height="80"> |
dot | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Cell/CellDot.gif" width="350" height="80"> |
attributed text | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Cell/TitleAttributed.gif" width="350" height="80"> |
mixed cells | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Cell/MixedCell.gif" width="350" height="80"> |
### Special Preview
Description | Gif |
----|------|
less data<br/> isItemSpacingAverageEnabled is true | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Special/ItemAveTrue.gif" width="350" height="80"> |
less data<br/> isItemSpacingAverageEnabled is false | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Special/ItemAveFalse.gif" width="350" height="80"> |
SegmentedControl<br/>reference[`SegmentedControlViewController`](https://github.com/pujiaxin33/JXSegmentedView/blob/master/JXSegmentedView/Special/SegmentedControl/SegmentedControlViewController.swift) | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Special/SegmentedControl.gif" width="350" height="80"> |
SegmentedControl<br/>reference[`SegmentedControlViewController`](https://github.com/pujiaxin33/JXSegmentedView/blob/master/JXSegmentedView/Special/SegmentedControl/SegmentedControlViewController.swift) | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Special/SegmentedControl.gif" width="350" height="80"> |
use in navigation bar <br/>reference[`NaviSegmentedControlViewController`](https://github.com/pujiaxin33/JXSegmentedView/blob/master/JXSegmentedView/Special/SegmentedControl/NaviSegmentedControlViewController.swift) | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Special/NavigationBar.gif" width="350" height="80"> |
nestable<br/>reference[`NestViewController`](https://github.com/pujiaxin33/JXSegmentedView/blob/master/JXSegmentedView/Special/Nest/NestViewController.swift) | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Special/Nest.gif" width="350" height="200"> |
user profile page<br/>reference[`PagingViewController`](https://github.com/pujiaxin33/JXSegmentedView/blob/master/JXSegmentedView/Special/Personal/PagingViewController.swift)<br/> more styles just click[JXPagingView](https://github.com/pujiaxin33/JXPagingView) | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Special/Personal.gif" width="350" height="567"> |
data load & refresh<br/>reference[`LoadDataViewController`](https://github.com/pujiaxin33/JXSegmentedView/blob/master/JXSegmentedView/Special/LoadData/WithListContainerView/LoadDataViewController.swift) | <img src="https://github.com/pujiaxin33/JXExampleImages/blob/master/JXSegmentedView/Special/LoadData.gif" width="350" height="200"> |
## Requirements
- iOS 9.0+
- Xcode 9+
- Swift 4.2、5.0
## Installation
### Manual
Clone the code and drag the Sources folder into the project to use it.
### CocoaPods
```ruby
target '<Your Target Name>' do
pod 'JXSegmentedView'
end
```
Execute `pod repo update` first, then execute `pod install`
## Usage
### `JXSegmentedView` example
1.JXSegmentedView initialize
```Swift
self.segmentedView = JXSegmentedView()
self.segmentedView.delegate = self
self.view.addSubview(self.segmentedView)
```
2.dataSource initialize
The `dataSouce` type is the `JXSegmentedViewDataSource` protocol. Use a separate class to implement the `JXSegmentedViewDataSource` protocol for code isolation. By selecting different class assignments to `dataSource`, you can control the `JXSegmentedView` display effect and implement plugin. For example, selecting the JXSegmentedTitleImageDataSource class as the dataSource selects the display effect of the text image; selecting the JXSegmentedNumberDataSource class as the dataSource selects the display effect of the text & number;
```Swift
//segmentedDataSource must be strongly held by the property, or it will be released
self.segmentedDataSource = JXSegmentedTitleDataSource()
//Configuring data source related properties
self.segmentedDataSource.titles = ["猴哥", "青蛙王子", "旺财"]
self.segmentedDataSource.isTitleColorGradientEnabled = true
//The reloadData(selectedIndex:) method must be called, and the method will internally refresh the data source array.
self.segmentedDataSource.reloadData(selectedIndex: 0)
//Associated dataSource
self.segmentedView.dataSource = self.segmentedDataSource
```
3.Indicator initialize
```Swift
let indicator = JXSegmentedIndicatorLineView()
indicator.indicatorWidth = 20
self.segmentedView.indicators = [indicator]
```
4.Implement `JXSegmentedViewDelegate`
```Swift
//This method is called when you click to select or scroll to select. Applicable to only care about selected events, regardless of whether it is click or scroll.
func segmentedView(_ segmentedView: JXSegmentedView, didSelectedItemAt index: Int) {}
// This method will be called when the selected condition is clicked.
func segmentedView(_ segmentedView: JXSegmentedView, didClickSelectedItemAt index: Int) {}
// This method is called when the scroll is selected.
func segmentedView(_ segmentedView: JXSegmentedView, didScrollSelectedItemAt index: Int) {}
// Then callback when scrolling
func segmentedView(_ segmentedView: JXSegmentedView, scrollingFrom leftIndex: Int, to rightIndex: Int, percent: CGFloat) {}
```
### `contentScrollView` list container usage example
#### Use the `UIScrollView` custom usage example directly
Because the code is scattered and the amount of code is large, it is not recommended. There are many places to pay attention to when using it properly, especially for students who are new to iOS.
Do not directly paste the code, click [LoadDataCustomViewController](https://github.com/pujiaxin33/JXSegmentedView/blob/master/JXSegmentedView/Special/LoadData/ListCustom/LoadDataCustomViewController.swift) to view the source code.
As an alternative, the official use & is highly recommended to use the following in this way 👇👇👇.
#### Use the example of the `JXSegmentedListContainerView` wrapper class
`JXSegmentedListContainerView` is a highly encapsulated class for list views with the following advantages:
- Compared to the direct use of `UIScrollView` customization, the package is high, the code is centralized, and the use is simple;
- List lazy loading: List initialization is only performed when a list is displayed. Instead of loading all the lists at once, the performance is better;
1.`JXSegmentedListContainerView` initialize
```Swift
self.listContainerView = JXSegmentedListContainerView(dataSource: self)
self.view.addSubview(self.listContainerView)
//Associate the cotentScrollView
self.segmentedView.contentScrollView = self.listContainerView.scrollView
```
2.Implement `JXSegmentedListContainerViewDataSource`
```Swift
//return numbers of lists
func numberOfLists(in listContainerView: JXSegmentedListContainerView) -> Int {
return self.segmentedDataSource.titles.count
}
//return the instance which comform `JXSegmentedListContainerViewListDelegate`
func listContainerView(_ listContainerView: JXSegmentedListContainerView, initListAt index: Int) -> JXSegmentedListContainerViewListDelegate {
return ListBaseViewController()
}
```
3.Implement `JXSegmentedListContainerViewListDelegate` for list
Regardless of whether the type of the list is UIView or UIViewController
```Swift
/// If the list is VC, return VC.view
/// If the list is View, return to View itself
/// - Returns: list
func listView() -> UIView {
return view
}
//Optional use, when the list is displayed
func listDidAppear() {}
//Optional use, called when the list disappears
func listDidDisappear() {}
```
4.Tell the key event `JXSegmentedListContainerView`
In the following two `JXSegmentedViewDelegate` proxy methods, call the corresponding code, don't forget this one❗
```Swift
func segmentedView(_ segmentedView: JXSegmentedView, didClickSelectedItemAt index: Int) {
//Pass the didClickSelectedItemAt event to the listContainerView, which must be called! ! !
listContainerView.didClickSelectedItem(at: index)
}
func segmentedView(_ segmentedView: JXSegmentedView, scrollingFrom leftIndex: Int, to rightIndex: Int, percent: CGFloat) {
//Pass the scrolling event to the listContainerView, which must be called! ! !
listContainerView.segmentedViewScrolling(from: leftIndex, to: rightIndex, percent: percent, selectedIndex: segmentedView.selectedIndex)
}
```
Click [LoadDataViewController](https://github.com/pujiaxin33/JXSegmentedView/blob/master/JXSegmentedView/Special/LoadData/WithListContainerView/LoadDataViewController.swift) to see the source code.
### Usage Summary
Because `JXSegmentedView` supports many features: indicators, cell styles, list containers, etc. How to manage the code orderly becomes a problem. The use of protocols, inheritance, and encapsulation classes greatly simplifies the use, and increases flexibility, making extensions quite easy.
- Core main class`JXSegmentedView`
- Data Source & Cell Style Custom Class: Classes that follow the `JXSegmentedViewDataSource` protocol
- Indicator class: `UIView` class that complies with the `JXSegmentedIndicatorProtocol` protocol
- List container: officially recommended `JXSegmentedListContainerView` class, special case can be customized using `UIScrollView`
### Indicator style customization
- To inherit the `JXSegmentedIndicatorProtocol` protocol, click on [JXSegmentedIndicatorProtocol](https://github.com/pujiaxin33/JXSegmentedView/blob/master/Sources/Indicator/JXSegmentedIndicatorProtocol.swift)
- The base class `JXSegmentedIndicatorBaseView` inheriting the `JXSegmentedIndicatorProtocol` protocol is provided, which provides many basic properties. Click to see [JXSegmentedIndicatorBaseView](https://github.com/pujiaxin33/JXSegmentedView/blob/master/Sources/Indicator/JXSegmentedIndicatorBaseView.swift)
- Custom indicator, please refer to the implemented indicator view, try more, think more, please ask Issue or join feedback QQ group if you have any questions.
### dataSource and Cell customization
- Need to inherit the `JXSegmentedViewDataSource` protocol, click on [JXSegmentedViewDataSource](https://github.com/pujiaxin33/JXSegmentedView/blob/master/Sources/Core/JXSegmentedView.swift)
- Provides the base class `JXSegmentedBaseDataSource` that inherits the `JXSegmentedViewDataSource` protocol, which provides many basic properties. Click to see [JXSegmentedBaseDataSource](https://github.com/pujiaxin33/JXSegmentedView/blob/master/Sources/Core/JXSegmentedBaseDataSource.swift)
- Any custom requirements, dataSource, cell, itemModel must be subclassed. Even if a subclass cell does nothing. Used to maintain the inheritance chain, so as not to know who to inherit after subclassing;
- dataSource and Cell customization, please refer to the implemented dataSource, try more, think more, please ask Issue or join feedback QQ group if you have any questions.
## Common Attribute Description
[Common attribute description document address](https://github.com/pujiaxin33/JXSegmentedView/blob/master/Document/English/property.md)
## Other Usage Tips
[Other usage tips document address](https://github.com/pujiaxin33/JXSegmentedView/blob/master/Document/English/tips.md)
## Supplement
If you are just starting to use `JXSegmentedView`, be sure to search for the documentation or source code when you need to support certain features during development. Confirm that you have implemented the features you want to support. Please don't ask the documentation and source code to see it, just ask questions directly. This is a waste of time for everyone. If you don't support the features you want, feel free to ask for an discussion, or implement a PullRequest yourself.
If you have any suggestions or questions, you can contact me by</br>
E-mail317437084@qq.com </br>
QQ Group 112440276
<img src="https://note.youdao.com/yws/public/resource/c6fa96a65e424afcf7f6304ddf5c283a/xmlnote/8dc821d271c35845acff3f853f434bce/3913" width="300" height="411">
If you like, just star❤ it.
## License
JXSegmentedView is released under the MIT license.

View File

@ -0,0 +1,41 @@
//
// JXSegmentedTitleAttributeCell.swift
// JXSegmentedView
//
// Created by jiaxin on 2019/1/3.
// Copyright © 2019 jiaxin. All rights reserved.
//
import UIKit
open class JXSegmentedTitleAttributeCell: JXSegmentedBaseCell {
open var titleLabel = UILabel()
open override func commonInit() {
super.commonInit()
titleLabel.textAlignment = .center
titleLabel.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(titleLabel)
let centerX = NSLayoutConstraint(item: titleLabel, attribute: .centerX, relatedBy: .equal, toItem: contentView, attribute: .centerX, multiplier: 1, constant: 0)
contentView.addConstraint(centerX)
let centerY = NSLayoutConstraint(item: titleLabel, attribute: .centerY, relatedBy: .equal, toItem: contentView, attribute: .centerY, multiplier: 1, constant: 0)
contentView.addConstraint(centerY)
}
open override func reloadData(itemModel: JXSegmentedBaseItemModel, selectedType: JXSegmentedViewItemSelectedType) {
super.reloadData(itemModel: itemModel, selectedType: selectedType )
guard let myItemModel = itemModel as? JXSegmentedTitleAttributeItemModel else {
return
}
titleLabel.numberOfLines = myItemModel.titleNumberOfLines
if myItemModel.isSelected && myItemModel.selectedAttributedTitle != nil {
titleLabel.attributedText = myItemModel.selectedAttributedTitle
}else {
titleLabel.attributedText = myItemModel.attributedTitle
}
}
}

View File

@ -0,0 +1,76 @@
//
// JXSegmentedTitleAttributeDataSource.swift
// JXSegmentedView
//
// Created by jiaxin on 2019/1/2.
// Copyright © 2019 jiaxin. All rights reserved.
//
import UIKit
open class JXSegmentedTitleAttributeDataSource: JXSegmentedBaseDataSource {
/// title
open var attributedTitles = [NSAttributedString]()
/// 使countattributedTitles
open var selectedAttributedTitles: [NSAttributedString]?
/// JXSegmentedViewUITableViewcellJXSegmentedViewreloadDatatitleUITableViewcellModeltitlesJXSegmentedView
open var widthForTitleClosure: ((NSAttributedString)->(CGFloat))?
/// titlenumberOfLines
open var titleNumberOfLines: Int = 2
open override func preferredItemModelInstance() -> JXSegmentedBaseItemModel {
return JXSegmentedTitleAttributeItemModel()
}
open override func preferredItemCount() -> Int {
return attributedTitles.count
}
open override func preferredRefreshItemModel(_ itemModel: JXSegmentedBaseItemModel, at index: Int, selectedIndex: Int) {
super.preferredRefreshItemModel(itemModel, at: index, selectedIndex: selectedIndex)
guard let myItemModel = itemModel as? JXSegmentedTitleAttributeItemModel else {
return
}
myItemModel.attributedTitle = attributedTitles[index]
myItemModel.selectedAttributedTitle = selectedAttributedTitles?[index]
myItemModel.textWidth = widthForTitle(myItemModel.attributedTitle, selectedTitle: myItemModel.selectedAttributedTitle)
myItemModel.titleNumberOfLines = titleNumberOfLines
}
open func widthForTitle(_ title: NSAttributedString?, selectedTitle: NSAttributedString?) -> CGFloat {
let attriText = selectedTitle != nil ? selectedTitle : title
guard let text = attriText else {
return 0
}
if widthForTitleClosure != nil {
return widthForTitleClosure!(text)
}else {
let textWidth = text.boundingRect(with: CGSize(width: CGFloat.infinity, height: CGFloat.infinity), options: NSStringDrawingOptions.init(rawValue: NSStringDrawingOptions.usesLineFragmentOrigin.rawValue | NSStringDrawingOptions.usesFontLeading.rawValue), context: nil).size.width
return CGFloat(ceilf(Float(textWidth)))
}
}
/// `preferredRefreshItemModel( _ itemModel: JXSegmentedBaseItemModel, at index: Int, selectedIndex: Int)`使
open override func preferredSegmentedView(_ segmentedView: JXSegmentedView, widthForItemAt index: Int) -> CGFloat {
var width: CGFloat = 0
if itemWidth == JXSegmentedViewAutomaticDimension {
let myItemModel = dataSource[index] as! JXSegmentedTitleAttributeItemModel
width = myItemModel.textWidth + itemWidthIncrement
}else {
width = itemWidth + itemWidthIncrement
}
return width
}
//MARK: - JXSegmentedViewDataSource
open override func registerCellClass(in segmentedView: JXSegmentedView) {
segmentedView.collectionView.register(JXSegmentedTitleAttributeCell.self, forCellWithReuseIdentifier: "cell")
}
open override func segmentedView(_ segmentedView: JXSegmentedView, cellForItemAt index: Int) -> JXSegmentedBaseCell {
let cell = segmentedView.dequeueReusableCell(withReuseIdentifier: "cell", at: index)
return cell
}
}

View File

@ -0,0 +1,16 @@
//
// JXSegmentedTitleAttributeItemModel.swift
// JXSegmentedView
//
// Created by jiaxin on 2019/1/3.
// Copyright © 2019 jiaxin. All rights reserved.
//
import UIKit
open class JXSegmentedTitleAttributeItemModel: JXSegmentedBaseItemModel {
open var attributedTitle: NSAttributedString?
open var selectedAttributedTitle: NSAttributedString?
open var titleNumberOfLines: Int = 0
open var textWidth: CGFloat = 0
}

View File

@ -0,0 +1,46 @@
//
// JXSegmentedAnimator.swift
// JXSegmentedView
//
// Created by jiaxin on 2019/1/21.
// Copyright © 2019 jiaxin. All rights reserved.
//
import Foundation
import UIKit
open class JXSegmentedAnimator {
open var duration: TimeInterval = 0.25
open var progressClosure: ((CGFloat)->())?
open var completedClosure: (()->())?
private var displayLink: CADisplayLink!
private var firstTimestamp: CFTimeInterval?
public init() {
displayLink = CADisplayLink(target: self, selector: #selector(processDisplayLink(sender:)))
}
open func start() {
displayLink.add(to: RunLoop.main, forMode: RunLoop.Mode.common)
}
open func stop() {
progressClosure?(1)
displayLink.invalidate()
completedClosure?()
}
@objc private func processDisplayLink(sender: CADisplayLink) {
if firstTimestamp == nil {
firstTimestamp = sender.timestamp
}
let percent = (sender.timestamp - firstTimestamp!)/duration
if percent >= 1 {
progressClosure?(1)
displayLink.invalidate()
completedClosure?()
}else {
progressClosure?(CGFloat(percent))
}
}
}

View File

@ -0,0 +1,512 @@
//
// JXSegmentedListContainerView.swift
// JXSegmentedView
//
// Created by jiaxin on 2018/12/26.
// Copyright © 2018 jiaxin. All rights reserved.
//
import UIKit
///
///- ScrollView: UIScrollViewUIScrollView
/// - CollectionView: 使UICollectionViewcellcell(MJRefresh)removeFromSuperview使scrollView type
public enum JXSegmentedListContainerType {
case scrollView
case collectionView
}
@objc
public protocol JXSegmentedListContainerViewListDelegate {
/// VCVC.view
/// ViewView
///
/// - Returns:
func listView() -> UIView
@objc optional func listWillAppear()
@objc optional func listDidAppear()
@objc optional func listWillDisappear()
@objc optional func listDidDisappear()
}
@objc
public protocol JXSegmentedListContainerViewDataSource {
/// list
func numberOfLists(in listContainerView: JXSegmentedListContainerView) -> Int
/// index`JXSegmentedListContainerViewListDelegate`
/// UIViewUIView`JXSegmentedListContainerViewListDelegate`UIView
/// UIViewControllerUIViewController`JXSegmentedListContainerViewListDelegate`UIViewController
///
///
/// - Parameters:
/// - listContainerView: JXSegmentedListContainerView
/// - index: index
/// - Returns: JXSegmentedListContainerViewListDelegate
func listContainerView(_ listContainerView: JXSegmentedListContainerView, initListAt index: Int) -> JXSegmentedListContainerViewListDelegate
/// index
@objc optional func listContainerView(_ listContainerView: JXSegmentedListContainerView, canInitListAt index: Int) -> Bool
/// UIScrollViewUICollectionViewClass
/// UIScrollViewFDFullscreenPopGesture
///
/// - Parameter listContainerView: JXSegmentedListContainerView
/// - Returns: UIScrollView
@objc optional func scrollViewClass(in listContainerView: JXSegmentedListContainerView) -> AnyClass
}
open class JXSegmentedListContainerView: UIView, JXSegmentedViewListContainer, JXSegmentedViewRTLCompatible {
open private(set) var type: JXSegmentedListContainerType
open private(set) weak var dataSource: JXSegmentedListContainerViewDataSource?
open private(set) var scrollView: UIScrollView!
/// keyindexvalue
open private(set) var validListDict = [Int:JXSegmentedListContainerViewListDelegate]()
/// 0.010~101
open var initListPercent: CGFloat = 0.01 {
didSet {
if initListPercent <= 0 || initListPercent >= 1 {
assertionFailure("initListPercent值范围为开区间(0,1)即不包括0和1")
}
}
}
open var listCellBackgroundColor: UIColor = .white
/// segmentedView.defaultSelectedIndexindex
open var defaultSelectedIndex: Int = 0 {
didSet {
currentIndex = defaultSelectedIndex
}
}
private var currentIndex: Int = 0
private lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
if let collectionViewClass = dataSource?.scrollViewClass?(in: self) as? UICollectionView.Type {
return collectionViewClass.init(frame: CGRect.zero, collectionViewLayout: layout)
}else {
return UICollectionView.init(frame: CGRect.zero, collectionViewLayout: layout)
}
}()
private lazy var containerVC = JXSegmentedListContainerViewController()
private var willAppearIndex: Int = -1
private var willDisappearIndex: Int = -1
public init(dataSource: JXSegmentedListContainerViewDataSource, type: JXSegmentedListContainerType = .scrollView) {
self.dataSource = dataSource
self.type = type
super.init(frame: CGRect.zero)
commonInit()
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
open func commonInit() {
containerVC.view.backgroundColor = .clear
addSubview(containerVC.view)
containerVC.viewWillAppearClosure = {[weak self] in
self?.listWillAppear(at: self?.currentIndex ?? 0)
}
containerVC.viewDidAppearClosure = {[weak self] in
self?.listDidAppear(at: self?.currentIndex ?? 0)
}
containerVC.viewWillDisappearClosure = {[weak self] in
self?.listWillDisappear(at: self?.currentIndex ?? 0)
}
containerVC.viewDidDisappearClosure = {[weak self] in
self?.listDidDisappear(at: self?.currentIndex ?? 0)
}
if type == .scrollView {
if let scrollViewClass = dataSource?.scrollViewClass?(in: self) as? UIScrollView.Type {
scrollView = scrollViewClass.init()
}else {
scrollView = UIScrollView.init()
}
scrollView.delegate = self
scrollView.isPagingEnabled = true
scrollView.showsVerticalScrollIndicator = false
scrollView.showsHorizontalScrollIndicator = false
scrollView.scrollsToTop = false
scrollView.bounces = false
if #available(iOS 11.0, *) {
scrollView.contentInsetAdjustmentBehavior = .never
}
if segmentedViewShouldRTLLayout() {
segmentedView(horizontalFlipForView: scrollView)
}
containerVC.view.addSubview(scrollView)
}else if type == .collectionView {
collectionView.isPagingEnabled = true
collectionView.showsHorizontalScrollIndicator = false
collectionView.showsVerticalScrollIndicator = false
collectionView.scrollsToTop = false
collectionView.bounces = false
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(JXSegmentedRTLCollectionCell.self, forCellWithReuseIdentifier: "cell")
if #available(iOS 10.0, *) {
collectionView.isPrefetchingEnabled = false
}
if #available(iOS 11.0, *) {
self.collectionView.contentInsetAdjustmentBehavior = .never
}
if segmentedViewShouldRTLLayout() {
collectionView.semanticContentAttribute = .forceLeftToRight
segmentedView(horizontalFlipForView: collectionView)
}
containerVC.view.addSubview(collectionView)
//访scrollView
scrollView = collectionView
}
}
open override func willMove(toSuperview newSuperview: UIView?) {
super.willMove(toSuperview: newSuperview)
var next: UIResponder? = newSuperview
while next != nil {
if let vc = next as? UIViewController{
vc.addChild(containerVC)
break
}
next = next?.next
}
}
open override func layoutSubviews() {
super.layoutSubviews()
containerVC.view.frame = bounds
guard let count = dataSource?.numberOfLists(in: self) else {
return
}
if type == .scrollView {
if scrollView.frame == CGRect.zero || scrollView.bounds.size != bounds.size {
scrollView.frame = bounds
scrollView.contentSize = CGSize(width: scrollView.bounds.size.width*CGFloat(count), height: scrollView.bounds.size.height)
for (index, list) in validListDict {
list.listView().frame = CGRect(x: CGFloat(index)*scrollView.bounds.size.width, y: 0, width: scrollView.bounds.size.width, height: scrollView.bounds.size.height)
}
scrollView.contentOffset = CGPoint(x: CGFloat(currentIndex)*scrollView.bounds.size.width, y: 0)
}else {
scrollView.frame = bounds
scrollView.contentSize = CGSize(width: scrollView.bounds.size.width*CGFloat(count), height: scrollView.bounds.size.height)
}
}else {
if collectionView.frame == CGRect.zero || collectionView.bounds.size != bounds.size {
collectionView.frame = bounds
collectionView.collectionViewLayout.invalidateLayout()
collectionView.setContentOffset(CGPoint(x: CGFloat(currentIndex)*collectionView.bounds.size.width, y: 0), animated: false)
}else {
collectionView.frame = bounds
}
}
}
//MARK: - JXSegmentedViewListContainer
public func contentScrollView() -> UIScrollView {
return scrollView
}
public func scrolling(from leftIndex: Int, to rightIndex: Int, percent: CGFloat, selectedIndex: Int) {
}
open func didClickSelectedItem(at index: Int) {
guard checkIndexValid(index) else {
return
}
willAppearIndex = -1
willDisappearIndex = -1
if currentIndex != index {
listWillDisappear(at: currentIndex)
listWillAppear(at: index)
listDidDisappear(at: currentIndex)
listDidAppear(at: index)
}
}
open func reloadData() {
guard let dataSource = dataSource else { return }
if currentIndex < 0 || currentIndex >= dataSource.numberOfLists(in: self) {
defaultSelectedIndex = 0
currentIndex = 0
}
validListDict.values.forEach { (list) in
if let listVC = list as? UIViewController {
listVC.removeFromParent()
}
list.listView().removeFromSuperview()
}
validListDict.removeAll()
if type == .scrollView {
scrollView.contentSize = CGSize(width: scrollView.bounds.size.width*CGFloat(dataSource.numberOfLists(in: self)), height: scrollView.bounds.size.height)
}else {
collectionView.reloadData()
}
listWillAppear(at: currentIndex)
listDidAppear(at: currentIndex)
}
//MARK: - Private
func initListIfNeeded(at index: Int) {
guard let dataSource = dataSource else { return }
if dataSource.listContainerView?(self, canInitListAt: index) == false {
return
}
var existedList = validListDict[index]
if existedList != nil {
//
return
}
existedList = dataSource.listContainerView(self, initListAt: index)
guard let list = existedList else {
return
}
if let vc = list as? UIViewController {
containerVC.addChild(vc)
}
validListDict[index] = list
if type == .scrollView {
list.listView().frame = CGRect(x: CGFloat(index)*scrollView.bounds.size.width, y: 0, width: scrollView.bounds.size.width, height: scrollView.bounds.size.height)
scrollView.addSubview(list.listView())
if segmentedViewShouldRTLLayout() {
segmentedView(horizontalFlipForView: list.listView())
}
}else {
let cell = collectionView.cellForItem(at: IndexPath(item: index, section: 0))
cell?.contentView.subviews.forEach { $0.removeFromSuperview() }
list.listView().frame = cell?.contentView.bounds ?? CGRect.zero
cell?.contentView.addSubview(list.listView())
}
}
private func listWillAppear(at index: Int) {
guard let dataSource = dataSource else { return }
guard checkIndexValid(index) else {
return
}
var existedList = validListDict[index]
if existedList != nil {
existedList?.listWillAppear?()
if let vc = existedList as? UIViewController {
vc.beginAppearanceTransition(true, animated: false)
}
}else {
//listWillAppear
guard dataSource.listContainerView?(self, canInitListAt: index) != false else {
return
}
existedList = dataSource.listContainerView(self, initListAt: index)
guard let list = existedList else {
return
}
if let vc = list as? UIViewController {
containerVC.addChild(vc)
}
validListDict[index] = list
if type == .scrollView {
if list.listView().superview == nil {
list.listView().frame = CGRect(x: CGFloat(index)*scrollView.bounds.size.width, y: 0, width: scrollView.bounds.size.width, height: scrollView.bounds.size.height)
scrollView.addSubview(list.listView())
if segmentedViewShouldRTLLayout() {
segmentedView(horizontalFlipForView: list.listView())
}
}
list.listWillAppear?()
if let vc = list as? UIViewController {
vc.beginAppearanceTransition(true, animated: false)
}
}else {
let cell = collectionView.cellForItem(at: IndexPath(item: index, section: 0))
cell?.contentView.subviews.forEach { $0.removeFromSuperview() }
list.listView().frame = cell?.contentView.bounds ?? CGRect.zero
cell?.contentView.addSubview(list.listView())
list.listWillAppear?()
if let vc = list as? UIViewController {
vc.beginAppearanceTransition(true, animated: false)
}
}
}
}
private func listDidAppear(at index: Int) {
guard checkIndexValid(index) else {
return
}
currentIndex = index
let list = validListDict[index]
list?.listDidAppear?()
if let vc = list as? UIViewController {
vc.endAppearanceTransition()
}
}
private func listWillDisappear(at index: Int) {
guard checkIndexValid(index) else {
return
}
let list = validListDict[index]
list?.listWillDisappear?()
if let vc = list as? UIViewController {
vc.beginAppearanceTransition(false, animated: false)
}
}
private func listDidDisappear(at index: Int) {
guard checkIndexValid(index) else {
return
}
let list = validListDict[index]
list?.listDidDisappear?()
if let vc = list as? UIViewController {
vc.endAppearanceTransition()
}
}
private func checkIndexValid(_ index: Int) -> Bool {
guard let dataSource = dataSource else { return false }
let count = dataSource.numberOfLists(in: self)
if count <= 0 || index >= count {
return false
}
return true
}
private func listDidAppearOrDisappear(scrollView: UIScrollView) {
let currentIndexPercent = scrollView.contentOffset.x/scrollView.bounds.size.width
if willAppearIndex != -1 || willDisappearIndex != -1 {
let disappearIndex = willDisappearIndex
let appearIndex = willAppearIndex
if willAppearIndex > willDisappearIndex {
//
if currentIndexPercent >= CGFloat(willAppearIndex) {
willDisappearIndex = -1
willAppearIndex = -1
listDidDisappear(at: disappearIndex)
listDidAppear(at: appearIndex)
}
}else {
//
if currentIndexPercent <= CGFloat(willAppearIndex) {
willDisappearIndex = -1
willAppearIndex = -1
listDidDisappear(at: disappearIndex)
listDidAppear(at: appearIndex)
}
}
}
}
}
extension JXSegmentedListContainerView: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
guard let dataSource = dataSource else { return 0 }
return dataSource.numberOfLists(in: self)
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.contentView.backgroundColor = listCellBackgroundColor
cell.contentView.subviews.forEach { $0.removeFromSuperview() }
let list = validListDict[indexPath.item]
if list != nil {
list?.listView().frame = cell.contentView.bounds
cell.contentView.addSubview(list!.listView())
}
return cell
}
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return bounds.size
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard scrollView.isTracking || scrollView.isDragging || scrollView.isDecelerating else {
return
}
let percent = scrollView.contentOffset.x/scrollView.bounds.size.width
let maxCount = Int(round(scrollView.contentSize.width/scrollView.bounds.size.width))
var leftIndex = Int(floor(Double(percent)))
leftIndex = max(0, min(maxCount - 1, leftIndex))
let rightIndex = leftIndex + 1;
if percent < 0 || rightIndex >= maxCount {
listDidAppearOrDisappear(scrollView: scrollView)
return
}
let remainderRatio = percent - CGFloat(leftIndex)
if rightIndex == currentIndex {
//
if validListDict[leftIndex] == nil && remainderRatio < (1 - initListPercent) {
initListIfNeeded(at: leftIndex)
}else if validListDict[leftIndex] != nil {
if willAppearIndex == -1 {
willAppearIndex = leftIndex;
listWillAppear(at: willAppearIndex)
}
}
if willDisappearIndex == -1 {
willDisappearIndex = rightIndex
listWillDisappear(at: willDisappearIndex)
}
}else {
//
if validListDict[rightIndex] == nil && remainderRatio > initListPercent {
initListIfNeeded(at: rightIndex)
}else if validListDict[rightIndex] != nil {
if willAppearIndex == -1 {
willAppearIndex = rightIndex
listWillAppear(at: willAppearIndex)
}
}
if willDisappearIndex == -1 {
willDisappearIndex = leftIndex
listWillDisappear(at: willDisappearIndex)
}
}
listDidAppearOrDisappear(scrollView: scrollView)
}
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
//
if willAppearIndex != -1 || willDisappearIndex != -1 {
listWillDisappear(at: willAppearIndex)
listWillAppear(at: willDisappearIndex)
listDidDisappear(at: willAppearIndex)
listDidAppear(at: willDisappearIndex)
willDisappearIndex = -1
willAppearIndex = -1
}
}
}
class JXSegmentedListContainerViewController: UIViewController {
var viewWillAppearClosure: (()->())?
var viewDidAppearClosure: (()->())?
var viewWillDisappearClosure: (()->())?
var viewDidDisappearClosure: (()->())?
override var shouldAutomaticallyForwardAppearanceMethods: Bool { return false }
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
viewWillAppearClosure?()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
viewDidAppearClosure?()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
viewWillDisappearClosure?()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
viewDidDisappearClosure?()
}
}

View File

@ -0,0 +1,50 @@
//
// JXSegmentedRTLLayout.swift
// JXSegmentedView
//
// Created by blue on 2020/6/18.
// Copyright © 2020 jiaxin. All rights reserved.
//
import UIKit
public protocol JXSegmentedViewRTLCompatible: class {
func segmentedViewShouldRTLLayout() -> Bool
func segmentedView(horizontalFlipForView view: UIView?)
}
public extension JXSegmentedViewRTLCompatible {
/// RTL
func segmentedViewShouldRTLLayout() -> Bool {
return UIView.userInterfaceLayoutDirection(for: UIView.appearance().semanticContentAttribute) == .rightToLeft
}
/// RTL
/// - Parameter view:
func segmentedView(horizontalFlipForView view: UIView?) {
view?.transform = CGAffineTransform(scaleX: -1, y: 1)
}
}
class JXSegmentedRTLCollectionCell: UICollectionViewCell, JXSegmentedViewRTLCompatible {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
if segmentedViewShouldRTLLayout() {
segmentedView(horizontalFlipForView: self)
segmentedView(horizontalFlipForView: contentView)
}
}
}

View File

@ -0,0 +1,76 @@
//
// JXSegmentedViewTool.swift
// JXSegmentedView
//
// Created by jiaxin on 2018/12/26.
// Copyright © 2018 jiaxin. All rights reserved.
//
import Foundation
import UIKit
public extension UIColor {
var jx_red: CGFloat {
var r: CGFloat = 0
getRed(&r, green: nil, blue: nil, alpha: nil)
return r
}
var jx_green: CGFloat {
var g: CGFloat = 0
getRed(nil, green: &g, blue: nil, alpha: nil)
return g
}
var jx_blue: CGFloat {
var b: CGFloat = 0
getRed(nil, green: nil, blue: &b, alpha: nil)
return b
}
var jx_alpha: CGFloat {
return cgColor.alpha
}
}
public class JXSegmentedViewTool {
public static func interpolate<T: SignedNumeric & Comparable>(from: T, to: T, percent: T) -> T {
let percent = max(0, min(1, percent))
return from + (to - from) * percent
}
public static func interpolateColor(from: UIColor, to: UIColor, percent: CGFloat) -> UIColor {
let r = interpolate(from: from.jx_red, to: to.jx_red, percent: percent)
let g = interpolate(from: from.jx_green, to: to.jx_green, percent: CGFloat(percent))
let b = interpolate(from: from.jx_blue, to: to.jx_blue, percent: CGFloat(percent))
let a = interpolate(from: from.jx_alpha, to: to.jx_alpha, percent: CGFloat(percent))
return UIColor(red: r, green: g, blue: b, alpha: a)
}
public static func interpolateColors(from: [CGColor], to: [CGColor], percent: CGFloat) -> [CGColor] {
var resultColors = [CGColor]()
for index in 0..<from.count {
let fromColor = UIColor(cgColor: from[index])
let toColor = UIColor(cgColor: to[index])
let r = interpolate(from: fromColor.jx_red, to: toColor.jx_red, percent: percent)
let g = interpolate(from: fromColor.jx_green, to: toColor.jx_green, percent: CGFloat(percent))
let b = interpolate(from: fromColor.jx_blue, to: toColor.jx_blue, percent: CGFloat(percent))
let a = interpolate(from: fromColor.jx_alpha, to: toColor.jx_alpha, percent: CGFloat(percent))
resultColors.append(UIColor(red: r, green: g, blue: b, alpha: a).cgColor)
}
return resultColors
}
}
extension JXSegmentedViewTool {
public static func interpolateThemeColor(from: UIColor, to: UIColor, percent: CGFloat) -> UIColor {
if #available(iOS 13.0, *) {
return UIColor { (traitCollection) -> UIColor in
let resolvedFrom = from.resolvedColor(with: traitCollection)
let resolvedTo = to.resolvedColor(with: traitCollection)
return interpolateColor(from: resolvedFrom, to: resolvedTo, percent: percent)
}
} else {
return interpolateColor(from: from, to: to, percent: percent)
}
}
}

View File

@ -0,0 +1,118 @@
//
// JXSegmentedBaseCell.swift
// JXSegmentedView
//
// Created by jiaxin on 2018/12/26.
// Copyright © 2018 jiaxin. All rights reserved.
//
import UIKit
public typealias JXSegmentedCellSelectedAnimationClosure = (CGFloat)->()
open class JXSegmentedBaseCell: UICollectionViewCell, JXSegmentedViewRTLCompatible {
open var itemModel: JXSegmentedBaseItemModel?
open var animator: JXSegmentedAnimator?
private var selectedAnimationClosureArray = [JXSegmentedCellSelectedAnimationClosure]()
deinit {
animator?.stop()
}
open override func prepareForReuse() {
super.prepareForReuse()
animator?.stop()
animator = nil
}
public override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
open func commonInit() {
if segmentedViewShouldRTLLayout() {
segmentedView(horizontalFlipForView: self)
segmentedView(horizontalFlipForView: contentView)
}
}
open func canStartSelectedAnimation(itemModel: JXSegmentedBaseItemModel, selectedType: JXSegmentedViewItemSelectedType) -> Bool {
var isSelectedAnimatable = false
if itemModel.isSelectedAnimable {
if selectedType == .scroll {
//
if !itemModel.isItemTransitionEnabled {
isSelectedAnimatable = true
}
}else if selectedType == .click || selectedType == .code {
//
isSelectedAnimatable = true
}
}
return isSelectedAnimatable
}
open func appendSelectedAnimationClosure(closure: @escaping JXSegmentedCellSelectedAnimationClosure) {
selectedAnimationClosureArray.append(closure)
}
open func startSelectedAnimationIfNeeded(itemModel: JXSegmentedBaseItemModel, selectedType: JXSegmentedViewItemSelectedType) {
if itemModel.isSelectedAnimable && canStartSelectedAnimation(itemModel: itemModel, selectedType: selectedType) {
//isTransitionAnimating
itemModel.isTransitionAnimating = true
animator?.progressClosure = {[weak self] (percent) in
guard self != nil else {
return
}
for closure in self!.selectedAnimationClosureArray {
closure(percent)
}
}
animator?.completedClosure = {[weak self] in
itemModel.isTransitionAnimating = false
self?.selectedAnimationClosureArray.removeAll()
}
animator?.start()
}
}
open func reloadData(itemModel: JXSegmentedBaseItemModel, selectedType: JXSegmentedViewItemSelectedType) {
self.itemModel = itemModel
if itemModel.isSelectedAnimable {
selectedAnimationClosureArray.removeAll()
if canStartSelectedAnimation(itemModel: itemModel, selectedType: selectedType) {
animator = JXSegmentedAnimator()
animator?.duration = itemModel.selectedAnimationDuration
}else {
animator?.stop()
animator = nil
}
}
}
open override var isSelected: Bool {
didSet {
setSelectedStyle(isSelected: isSelected)
}
}
open override var isHighlighted: Bool {
didSet {
setSelectedStyle(isSelected: isHighlighted)
}
}
func setSelectedStyle(isSelected: Bool) {
}
}

View File

@ -0,0 +1,174 @@
//
// JXSegmentedBaseDataSource.swift
// JXSegmentedView
//
// Created by jiaxin on 2018/12/28.
// Copyright © 2018 jiaxin. All rights reserved.
//
import Foundation
import UIKit
open class JXSegmentedBaseDataSource: JXSegmentedViewDataSource {
/// JXSegmentedView
open var dataSource = [JXSegmentedBaseItemModel]()
/// cellJXSegmentedViewAutomaticDimensionitemWidth
open var itemWidth: CGFloat = JXSegmentedViewAutomaticDimension
/// item = itemWidth + itemWidthIncrement
open var itemWidthIncrement: CGFloat = 0
/// item
open var itemSpacing: CGFloat = 20
/// collectionView.contentSize.widthJXSegmentedViewitemSpacing
open var isItemSpacingAverageEnabled: Bool = true
/// itemJXSegmentedTitleDataSourcetitleZoomtitleNormalColortitleStrokeWidth
open var isItemTransitionEnabled: Bool = true
/// cell`JXSegmentedTitleCell`
open var isSelectedAnimable: Bool = false
///
open var selectedAnimationDuration: TimeInterval = 0.25
/// item
open var isItemWidthZoomEnabled: Bool = false
/// item
open var isItemWidthZoomAnimable: Bool = true
/// itemscale
open var itemWidthSelectedZoomScale: CGFloat = 1.5
@available(*, deprecated, renamed: "itemWidth")
open var itemContentWidth: CGFloat = JXSegmentedViewAutomaticDimension {
didSet {
itemWidth = itemContentWidth
}
}
private var animator: JXSegmentedAnimator?
deinit {
animator?.stop()
}
public init() {
}
///
///
/// - Parameter selectedIndex: index
open func reloadData(selectedIndex: Int) {
dataSource.removeAll()
for index in 0..<preferredItemCount() {
let itemModel = preferredItemModelInstance()
preferredRefreshItemModel(itemModel, at: index, selectedIndex: selectedIndex)
dataSource.append(itemModel)
}
}
open func preferredItemCount() -> Int {
return 0
}
/// JXSegmentedBaseItemModel
open func preferredItemModelInstance() -> JXSegmentedBaseItemModel {
return JXSegmentedBaseItemModel()
}
/// indexitem
open func preferredSegmentedView(_ segmentedView: JXSegmentedView, widthForItemAt index: Int) -> CGFloat {
return itemWidthIncrement
}
/// indexitemModel
open func preferredRefreshItemModel(_ itemModel: JXSegmentedBaseItemModel, at index: Int, selectedIndex: Int) {
itemModel.index = index
itemModel.isItemTransitionEnabled = isItemTransitionEnabled
itemModel.isSelectedAnimable = isSelectedAnimable
itemModel.selectedAnimationDuration = selectedAnimationDuration
itemModel.isItemWidthZoomEnabled = isItemWidthZoomEnabled
itemModel.itemWidthNormalZoomScale = 1
itemModel.itemWidthSelectedZoomScale = itemWidthSelectedZoomScale
if index == selectedIndex {
itemModel.isSelected = true
itemModel.itemWidthCurrentZoomScale = itemModel.itemWidthSelectedZoomScale
}else {
itemModel.isSelected = false
itemModel.itemWidthCurrentZoomScale = itemModel.itemWidthNormalZoomScale
}
}
//MARK: - JXSegmentedViewDataSource
open func itemDataSource(in segmentedView: JXSegmentedView) -> [JXSegmentedBaseItemModel] {
return dataSource
}
/// `func preferredWidthForItem(at index: Int) -> CGFloat`
public final func segmentedView(_ segmentedView: JXSegmentedView, widthForItemAt index: Int) -> CGFloat {
return preferredSegmentedView(segmentedView, widthForItemAt: index)
}
public func segmentedView(_ segmentedView: JXSegmentedView, widthForItemContentAt index: Int) -> CGFloat {
return self.segmentedView(segmentedView, widthForItemAt: index)
}
open func registerCellClass(in segmentedView: JXSegmentedView) {
}
open func segmentedView(_ segmentedView: JXSegmentedView, cellForItemAt index: Int) -> JXSegmentedBaseCell {
return JXSegmentedBaseCell()
}
open func refreshItemModel(_ segmentedView: JXSegmentedView, currentSelectedItemModel: JXSegmentedBaseItemModel, willSelectedItemModel: JXSegmentedBaseItemModel, selectedType: JXSegmentedViewItemSelectedType) {
currentSelectedItemModel.isSelected = false
willSelectedItemModel.isSelected = true
if isItemWidthZoomEnabled {
if (selectedType == .scroll && !isItemTransitionEnabled) ||
selectedType == .click ||
selectedType == .code {
animator = JXSegmentedAnimator()
animator?.duration = selectedAnimationDuration
animator?.progressClosure = {[weak self] (percent) in
guard let self = self else { return }
currentSelectedItemModel.itemWidthCurrentZoomScale = JXSegmentedViewTool.interpolate(from: currentSelectedItemModel.itemWidthSelectedZoomScale, to: currentSelectedItemModel.itemWidthNormalZoomScale, percent: percent)
currentSelectedItemModel.itemWidth = self.itemWidthWithZoom(at: currentSelectedItemModel.index, model: currentSelectedItemModel)
willSelectedItemModel.itemWidthCurrentZoomScale = JXSegmentedViewTool.interpolate(from: willSelectedItemModel.itemWidthNormalZoomScale, to: willSelectedItemModel.itemWidthSelectedZoomScale, percent: percent)
willSelectedItemModel.itemWidth = self.itemWidthWithZoom(at: willSelectedItemModel.index, model: willSelectedItemModel)
segmentedView.collectionView.collectionViewLayout.invalidateLayout()
}
if isItemWidthZoomAnimable {
animator?.start()
}else {
animator?.stop()
}
}
}else {
currentSelectedItemModel.itemWidthCurrentZoomScale = currentSelectedItemModel.itemWidthNormalZoomScale
willSelectedItemModel.itemWidthCurrentZoomScale = willSelectedItemModel.itemWidthSelectedZoomScale
}
}
open func refreshItemModel(_ segmentedView: JXSegmentedView, leftItemModel: JXSegmentedBaseItemModel, rightItemModel: JXSegmentedBaseItemModel, percent: CGFloat) {
//itemWidthcontentScrollView
animator?.stop()
animator = nil
if isItemWidthZoomEnabled && isItemTransitionEnabled {
//itemWidthitem
leftItemModel.itemWidthCurrentZoomScale = JXSegmentedViewTool.interpolate(from: leftItemModel.itemWidthSelectedZoomScale, to: leftItemModel.itemWidthNormalZoomScale, percent: percent)
leftItemModel.itemWidth = itemWidthWithZoom(at: leftItemModel.index, model: leftItemModel)
rightItemModel.itemWidthCurrentZoomScale = JXSegmentedViewTool.interpolate(from: rightItemModel.itemWidthNormalZoomScale, to: rightItemModel.itemWidthSelectedZoomScale, percent: percent)
rightItemModel.itemWidth = itemWidthWithZoom(at: rightItemModel.index, model: rightItemModel)
segmentedView.collectionView.collectionViewLayout.invalidateLayout()
}
}
/// `func preferredRefreshItemModel(_ itemModel: JXSegmentedBaseItemModel, at index: Int, selectedIndex: Int)`
public final func refreshItemModel(_ segmentedView: JXSegmentedView, _ itemModel: JXSegmentedBaseItemModel, at index: Int, selectedIndex: Int) {
preferredRefreshItemModel(itemModel, at: index, selectedIndex: selectedIndex)
}
private func itemWidthWithZoom(at index: Int, model: JXSegmentedBaseItemModel) -> CGFloat {
var width = self.segmentedView(JXSegmentedView(), widthForItemAt: index)
if isItemWidthZoomEnabled {
width *= model.itemWidthCurrentZoomScale
}
return width
}
}

View File

@ -0,0 +1,30 @@
//
// JXSegmentedBaseItemModel.swift
// JXSegmentedView
//
// Created by jiaxin on 2018/12/26.
// Copyright © 2018 jiaxin. All rights reserved.
//
import Foundation
import UIKit
open class JXSegmentedBaseItemModel {
open var index: Int = 0
open var isSelected: Bool = false
open var itemWidth: CGFloat = 0
/// Framecell
open var indicatorConvertToItemFrame: CGRect = CGRect.zero
open var isItemTransitionEnabled: Bool = true
open var isSelectedAnimable: Bool = false
open var selectedAnimationDuration: TimeInterval = 0
///
open var isTransitionAnimating: Bool = false
open var isItemWidthZoomEnabled: Bool = false
open var itemWidthNormalZoomScale: CGFloat = 0
open var itemWidthCurrentZoomScale: CGFloat = 0
open var itemWidthSelectedZoomScale: CGFloat = 0
public init() {
}
}

View File

@ -0,0 +1,37 @@
//
// JXSegmentedCollectionView.swift
// JXSegmentedView
//
// Created by jiaxin on 2018/12/26.
// Copyright © 2018 jiaxin. All rights reserved.
//
import UIKit
open class JXSegmentedCollectionView: UICollectionView {
open var indicators = [JXSegmentedIndicatorProtocol]() {
willSet {
for indicator in indicators {
indicator.removeFromSuperview()
}
}
didSet {
for indicator in indicators {
addSubview(indicator)
}
}
}
open override func layoutSubviews() {
super.layoutSubviews()
for indicator in indicators {
sendSubviewToBack(indicator)
if let backgroundView = backgroundView {
sendSubviewToBack(backgroundView)
}
}
}
}

View File

@ -0,0 +1,730 @@
//
// JXSegmentedView.swift
// JXSegmentedView
//
// Created by jiaxin on 2018/12/26.
// Copyright © 2018 jiaxin. All rights reserved.
//
import UIKit
public let JXSegmentedViewAutomaticDimension: CGFloat = -1
/// item
///
/// - unknown:
/// - code: `func selectItemAt(index: Int)`
/// - click: item
/// - scroll: item
public enum JXSegmentedViewItemSelectedType {
case unknown
case code
case click
case scroll
}
public protocol JXSegmentedViewListContainer {
var defaultSelectedIndex: Int { set get }
func contentScrollView() -> UIScrollView
func reloadData()
func didClickSelectedItem(at index: Int)
}
public protocol JXSegmentedViewDataSource: AnyObject {
var isItemWidthZoomEnabled: Bool { get }
var selectedAnimationDuration: TimeInterval { get }
var itemSpacing: CGFloat { get }
var isItemSpacingAverageEnabled: Bool { get }
func reloadData(selectedIndex: Int)
/// JXSegmentedBaseItemModel
///
/// - Parameter segmentedView: JXSegmentedView
/// - Returns:
func itemDataSource(in segmentedView: JXSegmentedView) -> [JXSegmentedBaseItemModel]
/// indexitemcell
///
/// - Parameters:
/// - segmentedView: JXSegmentedView
/// - index: index
/// - Returns: item
func segmentedView(_ segmentedView: JXSegmentedView, widthForItemAt index: Int) -> CGFloat
/// indexitemcontentcell使cellitemcontent
/// - Parameters:
/// - segmentedView: JXSegmentedView
/// - index: index
func segmentedView(_ segmentedView: JXSegmentedView, widthForItemContentAt index: Int) -> CGFloat
/// cell class
///
/// - Parameter segmentedView: JXSegmentedView
func registerCellClass(in segmentedView: JXSegmentedView)
/// indexcell
///
/// - Parameters:
/// - segmentedView: JXSegmentedView
/// - index: index
/// - Returns: JXSegmentedBaseCell
func segmentedView(_ segmentedView: JXSegmentedView, cellForItemAt index: Int) -> JXSegmentedBaseCell
/// selectedIndexindexitemModel
///
/// - Parameters:
/// - itemModel: JXSegmentedBaseItemModel
/// - index: index
/// - selectedIndex: index
func refreshItemModel(_ segmentedView: JXSegmentedView, _ itemModel: JXSegmentedBaseItemModel, at index: Int, selectedIndex: Int)
/// itemcurrentSelectedItemModelwillSelectedItemModel
///
/// - Parameters:
/// - currentSelectedItemModel: itemModel
/// - willSelectedItemModel: itemModel
/// - selectedType:
func refreshItemModel(_ segmentedView: JXSegmentedView, currentSelectedItemModel: JXSegmentedBaseItemModel, willSelectedItemModel: JXSegmentedBaseItemModel, selectedType: JXSegmentedViewItemSelectedType)
/// leftItemModelrightItemModel
///
/// - Parameters:
/// - leftItemModel: itemModel
/// - rightItemModel: itemModel
/// - percent:
func refreshItemModel(_ segmentedView: JXSegmentedView, leftItemModel: JXSegmentedBaseItemModel, rightItemModel: JXSegmentedBaseItemModel, percent: CGFloat)
}
/// 使
public protocol JXSegmentedViewDelegate: AnyObject {
///
///
/// - Parameters:
/// - segmentedView: JXSegmentedView
/// - index: index
func segmentedView(_ segmentedView: JXSegmentedView, didSelectedItemAt index: Int)
///
///
/// - Parameters:
/// - segmentedView: JXSegmentedView
/// - index: index
func segmentedView(_ segmentedView: JXSegmentedView, didClickSelectedItemAt index: Int)
///
///
/// - Parameters:
/// - segmentedView: JXSegmentedView
/// - index: index
func segmentedView(_ segmentedView: JXSegmentedView, didScrollSelectedItemAt index: Int)
///
///
/// - Parameters:
/// - segmentedView: JXSegmentedView
/// - leftIndex: index
/// - rightIndex: index
/// - percent:
func segmentedView(_ segmentedView: JXSegmentedView, scrollingFrom leftIndex: Int, to rightIndex: Int, percent: CGFloat)
/// indexitem
///
/// - Parameters:
/// - segmentedView: JXSegmentedView
/// - index: index
func segmentedView(_ segmentedView: JXSegmentedView, canClickItemAt index: Int) -> Bool
}
/// JXSegmentedViewDelegateJXSegmentedViewDelegate
public extension JXSegmentedViewDelegate {
func segmentedView(_ segmentedView: JXSegmentedView, didSelectedItemAt index: Int) { }
func segmentedView(_ segmentedView: JXSegmentedView, didClickSelectedItemAt index: Int) { }
func segmentedView(_ segmentedView: JXSegmentedView, didScrollSelectedItemAt index: Int) { }
func segmentedView(_ segmentedView: JXSegmentedView, scrollingFrom leftIndex: Int, to rightIndex: Int, percent: CGFloat) { }
func segmentedView(_ segmentedView: JXSegmentedView, canClickItemAt index: Int) -> Bool { return true }
}
/// UIViewControllerautomaticallyAdjustsScrollViewInsetsfalse
open class JXSegmentedView: UIView, JXSegmentedViewRTLCompatible {
open weak var dataSource: JXSegmentedViewDataSource? {
didSet {
dataSource?.reloadData(selectedIndex: selectedIndex)
}
}
open weak var delegate: JXSegmentedViewDelegate?
open private(set) var collectionView: JXSegmentedCollectionView!
open var contentScrollView: UIScrollView? {
willSet {
contentScrollView?.removeObserver(self, forKeyPath: "contentOffset")
}
didSet {
contentScrollView?.scrollsToTop = false
contentScrollView?.addObserver(self, forKeyPath: "contentOffset", options: .new, context: nil)
}
}
public var listContainer: JXSegmentedViewListContainer? = nil {
didSet {
listContainer?.defaultSelectedIndex = defaultSelectedIndex
contentScrollView = listContainer?.contentScrollView()
}
}
/// indicatorsJXSegmentedIndicatorProtocolUIView
open var indicators = [JXSegmentedIndicatorProtocol]() {
didSet {
collectionView.indicators = indicators
}
}
/// reloadDataindex
open var defaultSelectedIndex: Int = 0 {
didSet {
selectedIndex = defaultSelectedIndex
if listContainer != nil {
listContainer?.defaultSelectedIndex = defaultSelectedIndex
}
}
}
open private(set) var selectedIndex: Int = 0
/// JXSegmentedViewAutomaticDimensionitemSpacing
open var contentEdgeInsetLeft: CGFloat = JXSegmentedViewAutomaticDimension
/// JXSegmentedViewAutomaticDimensionitemSpacing
open var contentEdgeInsetRight: CGFloat = JXSegmentedViewAutomaticDimension
/// contentScrollView
open var isContentScrollViewClickTransitionAnimationEnabled: Bool = true
private var itemDataSource = [JXSegmentedBaseItemModel]()
private var innerItemSpacing: CGFloat = 0
private var lastContentOffset: CGPoint = CGPoint.zero
/// indexitem
private var scrollingTargetIndex: Int = -1
private var isFirstLayoutSubviews = true
deinit {
contentScrollView?.removeObserver(self, forKeyPath: "contentOffset")
}
public override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
collectionView = JXSegmentedCollectionView(frame: CGRect.zero, collectionViewLayout: layout)
collectionView.backgroundColor = .clear
collectionView.showsVerticalScrollIndicator = false
collectionView.showsHorizontalScrollIndicator = false
collectionView.scrollsToTop = false
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "JXSegmentedViewInnerEmptyCell")
collectionView.dataSource = self
collectionView.delegate = self
if #available(iOS 10.0, *) {
collectionView.isPrefetchingEnabled = false
}
if #available(iOS 11.0, *) {
collectionView.contentInsetAdjustmentBehavior = .never
}
if segmentedViewShouldRTLLayout() {
collectionView.semanticContentAttribute = .forceLeftToRight
segmentedView(horizontalFlipForView: collectionView)
}
addSubview(collectionView)
}
open override func willMove(toSuperview newSuperview: UIView?) {
super.willMove(toSuperview: newSuperview)
var nextResponder: UIResponder? = newSuperview
while nextResponder != nil {
if let parentVC = nextResponder as? UIViewController {
parentVC.automaticallyAdjustsScrollViewInsets = false
break
}
nextResponder = nextResponder?.next
}
}
open override func layoutSubviews() {
super.layoutSubviews()
//使JXSegmentedViewUICollectionView
//JXSegmentedView
let targetFrame = CGRect(x: 0, y: 0, width: bounds.size.width, height: floor(bounds.size.height))
if isFirstLayoutSubviews {
isFirstLayoutSubviews = false
collectionView.frame = targetFrame
reloadDataWithoutListContainer()
}else {
if collectionView.frame != targetFrame {
collectionView.frame = targetFrame
collectionView.collectionViewLayout.invalidateLayout()
collectionView.reloadData()
}
}
}
//MARK: - Public
public final func dequeueReusableCell(withReuseIdentifier identifier: String, at index: Int) -> JXSegmentedBaseCell {
let indexPath = IndexPath(item: index, section: 0)
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath)
guard cell.isKind(of: JXSegmentedBaseCell.self) else {
fatalError("Cell class must be subclass of JXSegmentedBaseCell")
}
return cell as! JXSegmentedBaseCell
}
open func reloadData() {
reloadDataWithoutListContainer()
listContainer?.reloadData()
}
open func reloadDataWithoutListContainer() {
dataSource?.reloadData(selectedIndex: selectedIndex)
dataSource?.registerCellClass(in: self)
if let itemSource = dataSource?.itemDataSource(in: self) {
itemDataSource = itemSource
}
if selectedIndex < 0 || selectedIndex >= itemDataSource.count {
defaultSelectedIndex = 0
selectedIndex = 0
}
innerItemSpacing = dataSource?.itemSpacing ?? 0
var totalItemWidth: CGFloat = 0
var totalContentWidth: CGFloat = getContentEdgeInsetLeft()
for (index, itemModel) in itemDataSource.enumerated() {
itemModel.index = index
itemModel.itemWidth = (dataSource?.segmentedView(self, widthForItemAt: index) ?? 0)
if dataSource?.isItemWidthZoomEnabled == true {
itemModel.itemWidth *= itemModel.itemWidthCurrentZoomScale
}
itemModel.isSelected = (index == selectedIndex)
totalItemWidth += itemModel.itemWidth
if index == itemDataSource.count - 1 {
totalContentWidth += itemModel.itemWidth + getContentEdgeInsetRight()
}else {
totalContentWidth += itemModel.itemWidth + innerItemSpacing
}
}
if dataSource?.isItemSpacingAverageEnabled == true && totalContentWidth < bounds.size.width {
var itemSpacingCount = itemDataSource.count - 1
var totalItemSpacingWidth = bounds.size.width - totalItemWidth
if contentEdgeInsetLeft == JXSegmentedViewAutomaticDimension {
itemSpacingCount += 1
}else {
totalItemSpacingWidth -= contentEdgeInsetLeft
}
if contentEdgeInsetRight == JXSegmentedViewAutomaticDimension {
itemSpacingCount += 1
}else {
totalItemSpacingWidth -= contentEdgeInsetRight
}
if itemSpacingCount > 0 {
innerItemSpacing = totalItemSpacingWidth / CGFloat(itemSpacingCount)
}
}
var selectedItemFrameX = innerItemSpacing
var selectedItemWidth: CGFloat = 0
totalContentWidth = getContentEdgeInsetLeft()
for (index, itemModel) in itemDataSource.enumerated() {
if index < selectedIndex {
selectedItemFrameX += itemModel.itemWidth + innerItemSpacing
}else if index == selectedIndex {
selectedItemWidth = itemModel.itemWidth
}
if index == itemDataSource.count - 1 {
totalContentWidth += itemModel.itemWidth + getContentEdgeInsetRight()
}else {
totalContentWidth += itemModel.itemWidth + innerItemSpacing
}
}
let minX: CGFloat = 0
let maxX = totalContentWidth - bounds.size.width
let targetX = selectedItemFrameX - bounds.size.width/2 + selectedItemWidth/2
collectionView.setContentOffset(CGPoint(x: max(min(maxX, targetX), minX), y: 0), animated: false)
if contentScrollView != nil {
if contentScrollView!.frame.equalTo(CGRect.zero) &&
contentScrollView!.superview != nil {
//JXSegmentedViewcontentScrollViewdefaultSelectedIndexcontentScrollViewframezeroframelayoutSubviews
//JXSegmentedListContainerViewcontentScrollView使JXSegmentedListContainerView.superView
var parentView = contentScrollView?.superview
while parentView != nil && parentView?.frame.equalTo(CGRect.zero) == true {
parentView = parentView?.superview
}
parentView?.setNeedsLayout()
parentView?.layoutIfNeeded()
}
contentScrollView!.setContentOffset(CGPoint(x: CGFloat(selectedIndex) * contentScrollView!.bounds.size.width
, y: 0), animated: false)
}
for indicator in indicators {
if itemDataSource.isEmpty {
indicator.isHidden = true
}else {
indicator.isHidden = false
let selectedItemFrame = getItemFrameAt(index: selectedIndex)
let indicatorParams = JXSegmentedIndicatorSelectedParams(currentSelectedIndex: selectedIndex,
currentSelectedItemFrame: selectedItemFrame,
selectedType: .unknown,
currentItemContentWidth: dataSource?.segmentedView(self, widthForItemContentAt: selectedIndex) ?? 0,
collectionViewContentSize: CGSize(width: totalContentWidth, height: bounds.size.height))
indicator.refreshIndicatorState(model: indicatorParams)
if indicator.isIndicatorConvertToItemFrameEnabled {
var indicatorConvertToItemFrame = indicator.frame
indicatorConvertToItemFrame.origin.x -= selectedItemFrame.origin.x
itemDataSource[selectedIndex].indicatorConvertToItemFrame = indicatorConvertToItemFrame
}
}
}
collectionView.reloadData()
collectionView.collectionViewLayout.invalidateLayout()
}
open func reloadItem(at index: Int) {
guard index >= 0 && index < itemDataSource.count else {
return
}
dataSource?.refreshItemModel(self, itemDataSource[index], at: index, selectedIndex: selectedIndex)
let cell = collectionView.cellForItem(at: IndexPath(item: index, section: 0)) as? JXSegmentedBaseCell
cell?.reloadData(itemModel: itemDataSource[index], selectedType: .unknown)
}
/// index
/// index`listContainerView.didClickSelectedItem(at: index)`
///
/// - Parameter index: index
open func selectItemAt(index: Int) {
selectItemAt(index: index, selectedType: .code)
}
//MARK: - KVO
open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "contentOffset" {
let contentOffset = change?[NSKeyValueChangeKey.newKey] as! CGPoint
if contentScrollView?.isTracking == true || contentScrollView?.isDecelerating == true {
//contentOffset
if contentScrollView?.bounds.size.width == 0 {
// contentScrollView Frame
return
}
var progress = contentOffset.x/contentScrollView!.bounds.size.width
if Int(progress) > itemDataSource.count - 1 || progress < 0 {
//
return
}
if contentOffset.x == 0 && selectedIndex == 0 && lastContentOffset.x == 0 {
//contentOffset.x0
return
}
let maxContentOffsetX = contentScrollView!.contentSize.width - contentScrollView!.bounds.size.width
if contentOffset.x == maxContentOffsetX && selectedIndex == itemDataSource.count - 1 && lastContentOffset.x == maxContentOffsetX {
//contentOffset.xmaxContentOffsetX
return
}
progress = max(0, min(CGFloat(itemDataSource.count - 1), progress))
let baseIndex = Int(floor(progress))
let remainderProgress = progress - CGFloat(baseIndex)
let leftItemFrame = getItemFrameAt(index: baseIndex)
let rightItemFrame = getItemFrameAt(index: baseIndex + 1)
var rightItemContentWidth: CGFloat = 0
if baseIndex + 1 < itemDataSource.count {
rightItemContentWidth = dataSource?.segmentedView(self, widthForItemContentAt: baseIndex + 1) ?? 0
}
let indicatorParams = JXSegmentedIndicatorTransitionParams(currentSelectedIndex: selectedIndex,
leftIndex: baseIndex,
leftItemFrame: leftItemFrame,
leftItemContentWidth: dataSource?.segmentedView(self, widthForItemContentAt: baseIndex) ?? 0,
rightIndex: baseIndex + 1,
rightItemFrame: rightItemFrame,
rightItemContentWidth: rightItemContentWidth,
percent: remainderProgress)
if remainderProgress == 0 {
//
//contentOffsetindex1contentOffsetCGPoint(width, 0)
if !(lastContentOffset.x == contentOffset.x && selectedIndex == baseIndex) {
scrollSelectItemAt(index: baseIndex)
}
}else {
//remainderRatio0
if abs(progress - CGFloat(selectedIndex)) > 1 {
var targetIndex = baseIndex
if progress < CGFloat(selectedIndex) {
targetIndex = baseIndex + 1
}
scrollSelectItemAt(index: targetIndex)
}
if selectedIndex == baseIndex {
scrollingTargetIndex = baseIndex + 1
}else {
scrollingTargetIndex = baseIndex
}
dataSource?.refreshItemModel(self, leftItemModel: itemDataSource[baseIndex], rightItemModel: itemDataSource[baseIndex + 1], percent: remainderProgress)
for indicator in indicators {
indicator.contentScrollViewDidScroll(model: indicatorParams)
if indicator.isIndicatorConvertToItemFrameEnabled {
var leftIndicatorConvertToItemFrame = indicator.frame
leftIndicatorConvertToItemFrame.origin.x -= leftItemFrame.origin.x
itemDataSource[baseIndex].indicatorConvertToItemFrame = leftIndicatorConvertToItemFrame
var rightIndicatorConvertToItemFrame = indicator.frame
rightIndicatorConvertToItemFrame.origin.x -= rightItemFrame.origin.x
itemDataSource[baseIndex + 1].indicatorConvertToItemFrame = rightIndicatorConvertToItemFrame
}
}
let leftCell = collectionView.cellForItem(at: IndexPath(item: baseIndex, section: 0)) as? JXSegmentedBaseCell
leftCell?.reloadData(itemModel: itemDataSource[baseIndex], selectedType: .unknown)
let rightCell = collectionView.cellForItem(at: IndexPath(item: baseIndex + 1, section: 0)) as? JXSegmentedBaseCell
rightCell?.reloadData(itemModel: itemDataSource[baseIndex + 1], selectedType: .unknown)
delegate?.segmentedView(self, scrollingFrom: baseIndex, to: baseIndex + 1, percent: remainderProgress)
}
}
lastContentOffset = contentOffset
}
}
//MARK: - Private
private func clickSelectItemAt(index: Int) {
guard delegate?.segmentedView(self, canClickItemAt: index) != false else {
return
}
selectItemAt(index: index, selectedType: .click)
}
private func scrollSelectItemAt(index: Int) {
selectItemAt(index: index, selectedType: .scroll)
}
private func selectItemAt(index: Int, selectedType: JXSegmentedViewItemSelectedType) {
guard index >= 0 && index < itemDataSource.count else {
return
}
if index == selectedIndex {
if selectedType == .code {
listContainer?.didClickSelectedItem(at: index)
}else if selectedType == .click {
delegate?.segmentedView(self, didClickSelectedItemAt: index)
listContainer?.didClickSelectedItem(at: index)
}else if selectedType == .scroll {
delegate?.segmentedView(self, didScrollSelectedItemAt: index)
}
delegate?.segmentedView(self, didSelectedItemAt: index)
scrollingTargetIndex = -1
return
}
let currentSelectedItemModel = itemDataSource[selectedIndex]
let willSelectedItemModel = itemDataSource[index]
dataSource?.refreshItemModel(self, currentSelectedItemModel: currentSelectedItemModel, willSelectedItemModel: willSelectedItemModel, selectedType: selectedType)
let currentSelectedCell = collectionView.cellForItem(at: IndexPath(item: selectedIndex, section: 0)) as? JXSegmentedBaseCell
currentSelectedCell?.reloadData(itemModel: currentSelectedItemModel, selectedType: selectedType)
let willSelectedCell = collectionView.cellForItem(at: IndexPath(item: index, section: 0)) as? JXSegmentedBaseCell
willSelectedCell?.reloadData(itemModel: willSelectedItemModel, selectedType: selectedType)
if scrollingTargetIndex != -1 && scrollingTargetIndex != index {
let scrollingTargetItemModel = itemDataSource[scrollingTargetIndex]
scrollingTargetItemModel.isSelected = false
dataSource?.refreshItemModel(self, currentSelectedItemModel: scrollingTargetItemModel, willSelectedItemModel: willSelectedItemModel, selectedType: selectedType)
let scrollingTargetCell = collectionView.cellForItem(at: IndexPath(item: scrollingTargetIndex, section: 0)) as? JXSegmentedBaseCell
scrollingTargetCell?.reloadData(itemModel: scrollingTargetItemModel, selectedType: selectedType)
}
if dataSource?.isItemWidthZoomEnabled == true {
if selectedType == .click || selectedType == .code {
//cellwidthcellscrollToItembucellWidthindexcell
let selectedAnimationDurationInMilliseconds = Int((dataSource?.selectedAnimationDuration ?? 0)*1000)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + DispatchTimeInterval.milliseconds(selectedAnimationDurationInMilliseconds)) {
self.collectionView.scrollToItem(at: IndexPath(item: index, section: 0), at: .centeredHorizontally, animated: true)
}
}else if selectedType == .scroll {
//
collectionView.scrollToItem(at: IndexPath(item: index, section: 0), at: .centeredHorizontally, animated: true)
}
}else {
collectionView.scrollToItem(at: IndexPath(item: index, section: 0), at: .centeredHorizontally, animated: true)
}
if contentScrollView != nil && (selectedType == .click || selectedType == .code) {
contentScrollView!.setContentOffset(CGPoint(x: contentScrollView!.bounds.size.width*CGFloat(index), y: 0), animated: isContentScrollViewClickTransitionAnimationEnabled)
}
selectedIndex = index
let currentSelectedItemFrame = getSelectedItemFrameAt(index: selectedIndex)
for indicator in indicators {
let indicatorParams = JXSegmentedIndicatorSelectedParams(currentSelectedIndex: selectedIndex,
currentSelectedItemFrame: currentSelectedItemFrame,
selectedType: selectedType,
currentItemContentWidth: dataSource?.segmentedView(self, widthForItemContentAt: selectedIndex) ?? 0,
collectionViewContentSize: nil)
indicator.selectItem(model: indicatorParams)
if indicator.isIndicatorConvertToItemFrameEnabled {
var indicatorConvertToItemFrame = indicator.frame
indicatorConvertToItemFrame.origin.x -= currentSelectedItemFrame.origin.x
itemDataSource[selectedIndex].indicatorConvertToItemFrame = indicatorConvertToItemFrame
willSelectedCell?.reloadData(itemModel: willSelectedItemModel, selectedType: selectedType)
}
}
scrollingTargetIndex = -1
if selectedType == .code {
listContainer?.didClickSelectedItem(at: index)
}else if selectedType == .click {
delegate?.segmentedView(self, didClickSelectedItemAt: index)
listContainer?.didClickSelectedItem(at: index)
}else if selectedType == .scroll {
delegate?.segmentedView(self, didScrollSelectedItemAt: index)
}
delegate?.segmentedView(self, didSelectedItemAt: index)
}
private func getItemFrameAt(index: Int) -> CGRect {
guard index < itemDataSource.count else {
return CGRect.zero
}
var x = getContentEdgeInsetLeft()
for i in 0..<index {
let itemModel = itemDataSource[i]
var itemWidth: CGFloat = 0
if itemModel.isTransitionAnimating && itemModel.isItemWidthZoomEnabled {
//itemWidthCurrentZoomScale
if itemModel.isSelected {
itemWidth = (dataSource?.segmentedView(self, widthForItemAt: itemModel.index) ?? 0) * itemModel.itemWidthSelectedZoomScale
}else {
itemWidth = (dataSource?.segmentedView(self, widthForItemAt: itemModel.index) ?? 0) * itemModel.itemWidthNormalZoomScale
}
}else {
itemWidth = itemModel.itemWidth
}
x += itemWidth + innerItemSpacing
}
var width: CGFloat = 0
let selectedItemModel = itemDataSource[index]
if selectedItemModel.isTransitionAnimating && selectedItemModel.isItemWidthZoomEnabled {
width = (dataSource?.segmentedView(self, widthForItemAt: selectedItemModel.index) ?? 0) * selectedItemModel.itemWidthSelectedZoomScale
}else {
width = selectedItemModel.itemWidth
}
return CGRect(x: x, y: 0, width: width, height: bounds.size.height)
}
private func getSelectedItemFrameAt(index: Int) -> CGRect {
guard index < itemDataSource.count else {
return CGRect.zero
}
var x = getContentEdgeInsetLeft()
for i in 0..<index {
let itemWidth = (dataSource?.segmentedView(self, widthForItemAt: i) ?? 0)
x += itemWidth + innerItemSpacing
}
var width: CGFloat = 0
let selectedItemModel = itemDataSource[index]
if selectedItemModel.isItemWidthZoomEnabled {
width = (dataSource?.segmentedView(self, widthForItemAt: selectedItemModel.index) ?? 0) * selectedItemModel.itemWidthSelectedZoomScale
}else {
width = selectedItemModel.itemWidth
}
return CGRect(x: x, y: 0, width: width, height: bounds.size.height)
}
private func getContentEdgeInsetLeft() -> CGFloat {
if contentEdgeInsetLeft == JXSegmentedViewAutomaticDimension {
return innerItemSpacing
}else {
return contentEdgeInsetLeft
}
}
private func getContentEdgeInsetRight() -> CGFloat {
if contentEdgeInsetRight == JXSegmentedViewAutomaticDimension {
return innerItemSpacing
}else {
return contentEdgeInsetRight
}
}
}
extension JXSegmentedView: UICollectionViewDataSource {
public func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return itemDataSource.count
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = dataSource?.segmentedView(self, cellForItemAt: indexPath.item) {
cell.reloadData(itemModel: itemDataSource[indexPath.item], selectedType: .unknown)
return cell
}else {
return collectionView.dequeueReusableCell(withReuseIdentifier: "JXSegmentedViewInnerEmptyCell", for: indexPath)
}
}
}
extension JXSegmentedView: UICollectionViewDelegate {
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
var isTransitionAnimating = false
for itemModel in itemDataSource {
if itemModel.isTransitionAnimating {
isTransitionAnimating = true
break
}
}
if !isTransitionAnimating {
//item
clickSelectItemAt(index: indexPath.item)
}
}
}
extension JXSegmentedView: UICollectionViewDelegateFlowLayout {
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: getContentEdgeInsetLeft(), bottom: 0, right: getContentEdgeInsetRight())
}
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if indexPath.item >= 0, indexPath.item < itemDataSource.count {
return CGSize(width: itemDataSource[indexPath.item].itemWidth, height: collectionView.bounds.size.height)
} else {
return .zero
}
}
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return innerItemSpacing
}
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return innerItemSpacing
}
}

View File

@ -0,0 +1,42 @@
//
// JXSegmentedDotCell.swift
// JXSegmentedView
//
// Created by jiaxin on 2018/12/28.
// Copyright © 2018 jiaxin. All rights reserved.
//
import UIKit
open class JXSegmentedDotCell: JXSegmentedTitleCell {
open var dotView = UIView()
open override func commonInit() {
super.commonInit()
contentView.addSubview(dotView)
}
open override func layoutSubviews() {
super.layoutSubviews()
guard let myItemModel = itemModel as? JXSegmentedDotItemModel else {
return
}
dotView.center = CGPoint(x: titleLabel.frame.maxX + myItemModel.dotOffset.x, y: titleLabel.frame.minY + myItemModel.dotOffset.y)
}
open override func reloadData(itemModel: JXSegmentedBaseItemModel, selectedType: JXSegmentedViewItemSelectedType) {
super.reloadData(itemModel: itemModel, selectedType: selectedType )
guard let myItemModel = itemModel as? JXSegmentedDotItemModel else {
return
}
dotView.backgroundColor = myItemModel.dotColor
dotView.bounds = CGRect(x: 0, y: 0, width: myItemModel.dotSize.width, height: myItemModel.dotSize.height)
dotView.isHidden = !myItemModel.dotState
dotView.layer.cornerRadius = myItemModel.dotCornerRadius
}
}

View File

@ -0,0 +1,54 @@
//
// JXSegmentedDotDataSource.swift
// JXSegmentedView
//
// Created by jiaxin on 2018/12/28.
// Copyright © 2018 jiaxin. All rights reserved.
//
import UIKit
open class JXSegmentedDotDataSource: JXSegmentedTitleDataSource {
/// titles
open var dotStates = [Bool]()
/// size
open var dotSize = CGSize(width: 10, height: 10)
/// JXSegmentedViewAutomaticDimensiondotSize.height/2
open var dotCornerRadius: CGFloat = JXSegmentedViewAutomaticDimension
///
open var dotColor = UIColor.red
/// dotViewcentertitleLabeldotOffsetXY
open var dotOffset: CGPoint = CGPoint.zero
open override func preferredItemModelInstance() -> JXSegmentedBaseItemModel {
return JXSegmentedDotItemModel()
}
open override func preferredRefreshItemModel(_ itemModel: JXSegmentedBaseItemModel, at index: Int, selectedIndex: Int) {
super.preferredRefreshItemModel(itemModel, at: index, selectedIndex: selectedIndex)
guard let itemModel = itemModel as? JXSegmentedDotItemModel else {
return
}
itemModel.dotOffset = dotOffset
itemModel.dotState = dotStates[index]
itemModel.dotColor = dotColor
itemModel.dotSize = dotSize
if dotCornerRadius == JXSegmentedViewAutomaticDimension {
itemModel.dotCornerRadius = dotSize.height/2
}else {
itemModel.dotCornerRadius = dotCornerRadius
}
}
//MARK: - JXSegmentedViewDataSource
open override func registerCellClass(in segmentedView: JXSegmentedView) {
segmentedView.collectionView.register(JXSegmentedDotCell.self, forCellWithReuseIdentifier: "cell")
}
open override func segmentedView(_ segmentedView: JXSegmentedView, cellForItemAt index: Int) -> JXSegmentedBaseCell {
let cell = segmentedView.dequeueReusableCell(withReuseIdentifier: "cell", at: index)
return cell
}
}

View File

@ -0,0 +1,17 @@
//
// JXSegmentedDotItemModel.swift
// JXSegmentedView
//
// Created by jiaxin on 2018/12/28.
// Copyright © 2018 jiaxin. All rights reserved.
//
import UIKit
open class JXSegmentedDotItemModel: JXSegmentedTitleItemModel {
open var dotState = false
open var dotSize = CGSize.zero
open var dotCornerRadius: CGFloat = 0
open var dotColor = UIColor.red
open var dotOffset: CGPoint = CGPoint.zero
}

View File

@ -0,0 +1,19 @@
//
// JXSegmentedIndicatorGradientView.swift
// JXSegmentedView
//
// Created by jiaxin on 2019/1/2.
// Copyright © 2019 jiaxin. All rights reserved.
//
import UIKit
open class JXSegmentedComponetGradientView: UIView {
open class override var layerClass: AnyClass {
return CAGradientLayer.self
}
open var gradientLayer: CAGradientLayer {
return layer as! CAGradientLayer
}
}

View File

@ -0,0 +1,92 @@
//
// JXSegmentedIndicatorBackgroundView.swift
// JXSegmentedView
//
// Created by jiaxin on 2018/12/28.
// Copyright © 2018 jiaxin. All rights reserved.
//
import UIKit
/// indicatorPositionverticalOffset
open class JXSegmentedIndicatorBackgroundView: JXSegmentedIndicatorBaseView {
@available(*, deprecated, renamed: "indicatorWidthIncrement")
open var backgroundWidthIncrement: CGFloat = 20 {
didSet {
indicatorWidthIncrement = backgroundWidthIncrement
}
}
open override func commonInit() {
super.commonInit()
indicatorWidthIncrement = 20
indicatorHeight = 26
indicatorColor = .lightGray
indicatorPosition = .center
verticalOffset = 0
}
open override func refreshIndicatorState(model: JXSegmentedIndicatorSelectedParams) {
super.refreshIndicatorState(model: model)
backgroundColor = indicatorColor
layer.cornerRadius = getIndicatorCornerRadius(itemFrame: model.currentSelectedItemFrame)
let width = getIndicatorWidth(itemFrame: model.currentSelectedItemFrame, itemContentWidth: model.currentItemContentWidth)
let height = getIndicatorHeight(itemFrame: model.currentSelectedItemFrame)
let x = model.currentSelectedItemFrame.origin.x + (model.currentSelectedItemFrame.size.width - width)/2
var y: CGFloat = 0
switch indicatorPosition {
case .top:
y = verticalOffset
case .bottom:
y = model.currentSelectedItemFrame.size.height - height - verticalOffset
case .center:
y = (model.currentSelectedItemFrame.size.height - height)/2 + verticalOffset
}
frame = CGRect(x: x, y: y, width: width, height: height)
}
open override func contentScrollViewDidScroll(model: JXSegmentedIndicatorTransitionParams) {
super.contentScrollViewDidScroll(model: model)
guard canHandleTransition(model: model) else {
return
}
let rightItemFrame = model.rightItemFrame
let leftItemFrame = model.leftItemFrame
let percent = model.percent
var targetWidth = getIndicatorWidth(itemFrame: leftItemFrame, itemContentWidth: model.leftItemContentWidth)
let leftWidth = targetWidth
let rightWidth = getIndicatorWidth(itemFrame: rightItemFrame, itemContentWidth: model.rightItemContentWidth)
let leftX = leftItemFrame.origin.x + (leftItemFrame.size.width - leftWidth)/2
let rightX = rightItemFrame.origin.x + (rightItemFrame.size.width - rightWidth)/2
let targetX = JXSegmentedViewTool.interpolate(from: leftX, to: rightX, percent: CGFloat(percent))
if indicatorWidth == JXSegmentedViewAutomaticDimension {
targetWidth = JXSegmentedViewTool.interpolate(from: leftWidth, to: rightWidth, percent: CGFloat(percent))
}
self.frame.origin.x = targetX
self.frame.size.width = targetWidth
}
open override func selectItem(model: JXSegmentedIndicatorSelectedParams) {
super.selectItem(model: model)
let width = getIndicatorWidth(itemFrame: model.currentSelectedItemFrame, itemContentWidth: model.currentItemContentWidth)
var toFrame = self.frame
toFrame.origin.x = model.currentSelectedItemFrame.origin.x + (model.currentSelectedItemFrame.size.width - width)/2
toFrame.size.width = width
if canSelectedWithAnimation(model: model) {
UIView.animate(withDuration: scrollAnimationDuration, delay: 0, options: .curveEaseOut, animations: {
self.frame = toFrame
}) { (_) in
}
}else {
frame = toFrame
}
}
}

View File

@ -0,0 +1,108 @@
//
// JXSegmentedIndicatorBaseView.swift
// JXSegmentedView
//
// Created by jiaxin on 2018/12/26.
// Copyright © 2018 jiaxin. All rights reserved.
//
import UIKit
public enum JXSegmentedIndicatorPosition {
case top
case bottom
case center
}
open class JXSegmentedIndicatorBaseView: UIView, JXSegmentedIndicatorProtocol {
/// JXSegmentedViewAutomaticDimensioncellgetIndicatorWidth
open var indicatorWidth: CGFloat = JXSegmentedViewAutomaticDimension
open var indicatorWidthIncrement: CGFloat = 0 //cell10 point10=indicatorWidth+indicatorWidthIncrement
/// JXSegmentedViewAutomaticDimensioncellgetIndicatorHeight
open var indicatorHeight: CGFloat = JXSegmentedViewAutomaticDimension
/// JXSegmentedViewAutomaticDimension indicatorHeight/2getIndicatorCornerRadius
open var indicatorCornerRadius: CGFloat = JXSegmentedViewAutomaticDimension
///
open var indicatorColor: UIColor = .red
/// topbottomcenter
open var indicatorPosition: JXSegmentedIndicatorPosition = .bottom
/// verticalOffset
open var verticalOffset: CGFloat = 0
///
open var isScrollEnabled: Bool = true
/// indicatorframecellJXSegmentedTitleDataSourcedisTitleMaskEnabled使
/// indicatorindicatorisIndicatorConvertToItemFrameEnabledtrue
/// indicatorisIndicatorConvertToItemFrameEnabledtrueisIndicatorConvertToItemFrameEnabledtrueindicator
open var isIndicatorConvertToItemFrameEnabled: Bool = true
///
open var scrollAnimationDuration: TimeInterval = 0.25
/// itemcellindicatorWidth=JXSegmentedViewAutomaticDimension
open var isIndicatorWidthSameAsItemContent = false
public override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
open func commonInit() {
}
public func getIndicatorCornerRadius(itemFrame: CGRect) -> CGFloat {
if indicatorCornerRadius == JXSegmentedViewAutomaticDimension {
return getIndicatorHeight(itemFrame: itemFrame)/2
}
return indicatorCornerRadius
}
public func getIndicatorWidth(itemFrame: CGRect, itemContentWidth: CGFloat) -> CGFloat {
if indicatorWidth == JXSegmentedViewAutomaticDimension {
if isIndicatorWidthSameAsItemContent {
return itemContentWidth + indicatorWidthIncrement
}else {
return itemFrame.size.width + indicatorWidthIncrement
}
}
return indicatorWidth + indicatorWidthIncrement
}
public func getIndicatorHeight(itemFrame: CGRect) -> CGFloat {
if indicatorHeight == JXSegmentedViewAutomaticDimension {
return itemFrame.size.height
}
return indicatorHeight
}
public func canHandleTransition(model: JXSegmentedIndicatorTransitionParams) -> Bool {
if model.percent == 0 || !isScrollEnabled {
//model.percent0selectItem(model: JXSegmentedIndicatorParamsModel)
//isScrollEnabledfalse
return false
}
return true
}
public func canSelectedWithAnimation(model: JXSegmentedIndicatorSelectedParams) -> Bool {
if isScrollEnabled && (model.selectedType == .click || model.selectedType == .code) {
//
return true
}
return false
}
//MARK: - JXSegmentedIndicatorProtocol
open func refreshIndicatorState(model: JXSegmentedIndicatorSelectedParams) {
}
open func contentScrollViewDidScroll(model: JXSegmentedIndicatorTransitionParams) {
}
open func selectItem(model: JXSegmentedIndicatorSelectedParams) {
}
}

View File

@ -0,0 +1,94 @@
//
// JXSegmentedIndicatorDotLineView.swift
// JXSegmentedView
//
// Created by jiaxin on 2019/1/16.
// Copyright © 2019 jiaxin. All rights reserved.
//
import UIKit
open class JXSegmentedIndicatorDotLineView: JXSegmentedIndicatorBaseView {
/// 线
open var lineMaxWidth: CGFloat = 50
open override func commonInit() {
super.commonInit()
//size
indicatorWidth = 10
indicatorHeight = 10
}
open override func refreshIndicatorState(model: JXSegmentedIndicatorSelectedParams) {
super.refreshIndicatorState(model: model)
backgroundColor = indicatorColor
layer.cornerRadius = getIndicatorCornerRadius(itemFrame: model.currentSelectedItemFrame)
let width = getIndicatorWidth(itemFrame: model.currentSelectedItemFrame, itemContentWidth: model.currentItemContentWidth)
let height = getIndicatorHeight(itemFrame: model.currentSelectedItemFrame)
let x = model.currentSelectedItemFrame.origin.x + (model.currentSelectedItemFrame.size.width - width)/2
var y: CGFloat = 0
switch indicatorPosition {
case .top:
y = verticalOffset
case .bottom:
y = model.currentSelectedItemFrame.size.height - height - verticalOffset
case .center:
y = (model.currentSelectedItemFrame.size.height - height)/2 + verticalOffset
}
frame = CGRect(x: x, y: y, width: width, height: height)
}
open override func contentScrollViewDidScroll(model: JXSegmentedIndicatorTransitionParams) {
super.contentScrollViewDidScroll(model: model)
guard canHandleTransition(model: model) else {
return
}
let rightItemFrame = model.rightItemFrame
let leftItemFrame = model.leftItemFrame
let percent = model.percent
var targetX: CGFloat = leftItemFrame.origin.x
let dotWidth = getIndicatorWidth(itemFrame: leftItemFrame, itemContentWidth: model.leftItemContentWidth)
var targetWidth = dotWidth
let leftWidth = targetWidth
let rightWidth = getIndicatorWidth(itemFrame: rightItemFrame, itemContentWidth: model.rightItemContentWidth)
let leftX = leftItemFrame.origin.x + (leftItemFrame.size.width - leftWidth)/2
let rightX = rightItemFrame.origin.x + (rightItemFrame.size.width - rightWidth)/2
let centerX = leftX + (rightX - leftX - lineMaxWidth)/2
//50%x50%xwidth
if percent <= 0.5 {
targetX = JXSegmentedViewTool.interpolate(from: leftX, to: centerX, percent: CGFloat(percent*2))
targetWidth = JXSegmentedViewTool.interpolate(from: dotWidth, to: lineMaxWidth, percent: CGFloat(percent*2))
}else {
targetX = JXSegmentedViewTool.interpolate(from: centerX, to: rightX, percent: CGFloat((percent - 0.5)*2))
targetWidth = JXSegmentedViewTool.interpolate(from: lineMaxWidth, to: dotWidth, percent: CGFloat((percent - 0.5)*2))
}
self.frame.origin.x = targetX
self.frame.size.width = targetWidth
}
open override func selectItem(model: JXSegmentedIndicatorSelectedParams) {
super.selectItem(model: model)
let targetWidth = getIndicatorWidth(itemFrame: model.currentSelectedItemFrame, itemContentWidth: model.currentItemContentWidth)
var toFrame = self.frame
toFrame.origin.x = model.currentSelectedItemFrame.origin.x + (model.currentSelectedItemFrame.size.width - targetWidth)/2
toFrame.size.width = targetWidth
if canSelectedWithAnimation(model: model) {
UIView.animate(withDuration: scrollAnimationDuration, delay: 0, options: .curveEaseOut, animations: {
self.frame = toFrame
}) { (_) in
}
}else {
frame = toFrame
}
}
}

View File

@ -0,0 +1,109 @@
//
// JXSegmentedIndicatorDoubleLineView.swift
// JXSegmentedView
//
// Created by jiaxin on 2019/1/16.
// Copyright © 2019 jiaxin. All rights reserved.
//
import UIKit
open class JXSegmentedIndicatorDoubleLineView: JXSegmentedIndicatorBaseView {
/// 线
open var minLineWidthPercent: CGFloat = 0.2
public let selectedLineView: UIView = UIView()
public let otherLineView: UIView = UIView()
open override func commonInit() {
super.commonInit()
indicatorHeight = 3
addSubview(selectedLineView)
otherLineView.alpha = 0
addSubview(otherLineView)
}
open override func refreshIndicatorState(model: JXSegmentedIndicatorSelectedParams) {
super.refreshIndicatorState(model: model)
selectedLineView.backgroundColor = indicatorColor
otherLineView.backgroundColor = indicatorColor
selectedLineView.layer.cornerRadius = getIndicatorCornerRadius(itemFrame: model.currentSelectedItemFrame)
otherLineView.layer.cornerRadius = getIndicatorCornerRadius(itemFrame: model.currentSelectedItemFrame)
let width = getIndicatorWidth(itemFrame: model.currentSelectedItemFrame, itemContentWidth: model.currentItemContentWidth)
let height = getIndicatorHeight(itemFrame: model.currentSelectedItemFrame)
let x = model.currentSelectedItemFrame.origin.x + (model.currentSelectedItemFrame.size.width - width)/2
var y: CGFloat = 0
switch indicatorPosition {
case .top:
y = verticalOffset
case .bottom:
y = model.currentSelectedItemFrame.size.height - height - verticalOffset
case .center:
y = (model.currentSelectedItemFrame.size.height - height)/2 + verticalOffset
}
selectedLineView.frame = CGRect(x: x, y: y, width: width, height: height)
otherLineView.frame = selectedLineView.frame
}
open override func contentScrollViewDidScroll(model: JXSegmentedIndicatorTransitionParams) {
super.contentScrollViewDidScroll(model: model)
guard canHandleTransition(model: model) else {
return
}
let rightItemFrame = model.rightItemFrame
let leftItemFrame = model.leftItemFrame
let percent = model.percent
let leftCenter = getCenter(in: leftItemFrame)
let rightCenter = getCenter(in: rightItemFrame)
let leftMaxWidth = getIndicatorWidth(itemFrame: leftItemFrame, itemContentWidth: model.leftItemContentWidth)
let rightMaxWidth = getIndicatorWidth(itemFrame: rightItemFrame, itemContentWidth: model.rightItemContentWidth)
let leftMinWidth = leftMaxWidth*minLineWidthPercent
let rightMinWidth = rightMaxWidth*minLineWidthPercent
let leftWidth: CGFloat = JXSegmentedViewTool.interpolate(from: leftMaxWidth, to: leftMinWidth, percent: CGFloat(percent))
let rightWidth: CGFloat = JXSegmentedViewTool.interpolate(from: rightMinWidth, to: rightMaxWidth, percent: CGFloat(percent))
let leftAlpha: CGFloat = JXSegmentedViewTool.interpolate(from: 1, to: 0, percent: CGFloat(percent))
let rightAlpha: CGFloat = JXSegmentedViewTool.interpolate(from: 0, to: 1, percent: CGFloat(percent))
if model.currentSelectedIndex == model.leftIndex {
selectedLineView.bounds.size.width = leftWidth
selectedLineView.center = leftCenter
selectedLineView.alpha = leftAlpha
otherLineView.bounds.size.width = rightWidth
otherLineView.center = rightCenter
otherLineView.alpha = rightAlpha
}else {
otherLineView.bounds.size.width = leftWidth
otherLineView.center = leftCenter
otherLineView.alpha = leftAlpha
selectedLineView.bounds.size.width = rightWidth
selectedLineView.center = rightCenter
selectedLineView.alpha = rightAlpha
}
}
open override func selectItem(model: JXSegmentedIndicatorSelectedParams) {
super.selectItem(model: model)
let targetWidth = getIndicatorWidth(itemFrame: model.currentSelectedItemFrame, itemContentWidth: model.currentItemContentWidth)
let targetCenter = getCenter(in: model.currentSelectedItemFrame)
selectedLineView.bounds.size.width = targetWidth
selectedLineView.center = targetCenter
selectedLineView.alpha = 1
otherLineView.alpha = 0
}
private func getCenter(in frame: CGRect) -> CGPoint {
return CGPoint(x: frame.midX, y: selectedLineView.center.y)
}
}

View File

@ -0,0 +1,63 @@
//
// JXSegmentedIndicatorGradientLineView.swift
// JXSegmentedView
//
// Created by jiaxin on 2020/7/6.
// Copyright © 2020 jiaxin. All rights reserved.
//
import UIKit
/// indicatorColorgradientColors
open class JXSegmentedIndicatorGradientLineView: JXSegmentedIndicatorLineView {
open var colors = [UIColor]()
open var startPoint = CGPoint.zero
open var endPoint = CGPoint(x: 1, y: 0)
open var locations: [NSNumber]?
public let gradientLayer = CAGradientLayer()
open override func commonInit() {
super.commonInit()
layer.masksToBounds = true
layer.addSublayer(gradientLayer)
}
open override func refreshIndicatorState(model: JXSegmentedIndicatorSelectedParams) {
super.refreshIndicatorState(model: model)
backgroundColor = .clear
CATransaction.begin()
CATransaction.setDisableActions(true)
gradientLayer.frame = bounds
gradientLayer.colors = colors.map { $0.cgColor }
gradientLayer.startPoint = startPoint
gradientLayer.endPoint = endPoint
gradientLayer.locations = locations
CATransaction.commit()
}
open override func contentScrollViewDidScroll(model: JXSegmentedIndicatorTransitionParams) {
super.contentScrollViewDidScroll(model: model)
guard canHandleTransition(model: model) else {
return
}
CATransaction.begin()
CATransaction.setDisableActions(true)
gradientLayer.frame = bounds
CATransaction.commit()
}
open override func selectItem(model: JXSegmentedIndicatorSelectedParams) {
super.selectItem(model: model)
let targetWidth = getIndicatorWidth(itemFrame: model.currentSelectedItemFrame, itemContentWidth: model.currentItemContentWidth)
CATransaction.begin()
CATransaction.setAnimationDuration(scrollAnimationDuration)
CATransaction.setAnimationTimingFunction(.init(name: .easeOut))
gradientLayer.frame.size.width = targetWidth
CATransaction.commit()
}
}

View File

@ -0,0 +1,131 @@
//
// JXSegmentedIndicatorGradientView.swift
// JXSegmentedView
//
// Created by jiaxin on 2019/1/16.
// Copyright © 2019 jiaxin. All rights reserved.
//
import UIKit
/// layergradientMaskLayer
open class JXSegmentedIndicatorGradientView: JXSegmentedIndicatorBaseView {
@available(*, deprecated, renamed: "indicatorWidthIncrement")
open var gradientViewWidthIncrement: CGFloat = 20 {
didSet {
indicatorWidthIncrement = gradientViewWidthIncrement
}
}
/// colors
open var gradientColors = [CGColor]()
/// CAGradientLayerstartPointendPoint
open var gradientLayer: CAGradientLayer {
return layer as! CAGradientLayer
}
public let gradientMaskLayer: CAShapeLayer = CAShapeLayer()
open class override var layerClass: AnyClass {
return CAGradientLayer.self
}
private var gradientMaskLayerFrame = CGRect.zero
open override func commonInit() {
super.commonInit()
indicatorWidthIncrement = 20
indicatorHeight = 26
indicatorPosition = .center
verticalOffset = 0
gradientColors = [UIColor(red: 194.0/255, green: 229.0/255, blue: 156.0/255, alpha: 1).cgColor, UIColor(red: 100.0/255, green: 179.0/255, blue: 244.0/255, alpha: 1).cgColor]
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x: 1, y: 0)
layer.mask = gradientMaskLayer
}
open override func refreshIndicatorState(model: JXSegmentedIndicatorSelectedParams) {
super.refreshIndicatorState(model: model)
gradientLayer.colors = gradientColors
let width = getIndicatorWidth(itemFrame: model.currentSelectedItemFrame, itemContentWidth: model.currentItemContentWidth)
let height = getIndicatorHeight(itemFrame: model.currentSelectedItemFrame)
let x = model.currentSelectedItemFrame.origin.x + (model.currentSelectedItemFrame.size.width - width)/2
var y: CGFloat = 0
switch indicatorPosition {
case .top:
y = verticalOffset
case .bottom:
y = model.currentSelectedItemFrame.size.height - height - verticalOffset
case .center:
y = (model.currentSelectedItemFrame.size.height - height)/2 + verticalOffset
}
gradientMaskLayerFrame = CGRect(x: x, y: y, width: width, height: height)
let path = UIBezierPath(roundedRect: gradientMaskLayerFrame, cornerRadius: getIndicatorCornerRadius(itemFrame: model.currentSelectedItemFrame))
CATransaction.begin()
CATransaction.setDisableActions(true)
gradientMaskLayer.path = path.cgPath
CATransaction.commit()
if let collectionViewContentSize = model.collectionViewContentSize {
frame = CGRect(x: 0, y: 0, width: collectionViewContentSize.width, height: collectionViewContentSize.height)
}
}
open override func contentScrollViewDidScroll(model: JXSegmentedIndicatorTransitionParams) {
super.contentScrollViewDidScroll(model: model)
guard canHandleTransition(model: model) else {
return
}
let rightItemFrame = model.rightItemFrame
let leftItemFrame = model.leftItemFrame
let percent = model.percent
var targetWidth = getIndicatorWidth(itemFrame: leftItemFrame, itemContentWidth: model.leftItemContentWidth)
let leftWidth = targetWidth
let rightWidth = getIndicatorWidth(itemFrame: rightItemFrame, itemContentWidth: model.rightItemContentWidth)
let leftX = leftItemFrame.origin.x + (leftItemFrame.size.width - leftWidth)/2
let rightX = rightItemFrame.origin.x + (rightItemFrame.size.width - rightWidth)/2
let targetX = JXSegmentedViewTool.interpolate(from: leftX, to: rightX, percent: CGFloat(percent))
if indicatorWidth == JXSegmentedViewAutomaticDimension {
targetWidth = JXSegmentedViewTool.interpolate(from: leftWidth, to: rightWidth, percent: CGFloat(percent))
}
gradientMaskLayerFrame.origin.x = targetX
gradientMaskLayerFrame.size.width = targetWidth
let path = UIBezierPath(roundedRect: gradientMaskLayerFrame, cornerRadius: getIndicatorCornerRadius(itemFrame: leftItemFrame))
CATransaction.begin()
CATransaction.setDisableActions(true)
gradientMaskLayer.path = path.cgPath
CATransaction.commit()
}
open override func selectItem(model: JXSegmentedIndicatorSelectedParams) {
super.selectItem(model: model)
let width = getIndicatorWidth(itemFrame: model.currentSelectedItemFrame, itemContentWidth: model.currentItemContentWidth)
var toFrame = gradientMaskLayerFrame
toFrame.origin.x = model.currentSelectedItemFrame.origin.x + (model.currentSelectedItemFrame.size.width - width)/2
toFrame.size.width = width
let path = UIBezierPath(roundedRect: toFrame, cornerRadius: getIndicatorCornerRadius(itemFrame: model.currentSelectedItemFrame))
if canSelectedWithAnimation(model: model) {
gradientMaskLayer.removeAnimation(forKey: "path")
let animation = CABasicAnimation(keyPath: "path")
animation.fromValue = gradientMaskLayer.path
animation.toValue = path.cgPath
animation.duration = scrollAnimationDuration
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
gradientMaskLayer.add(animation, forKey: "path")
gradientMaskLayer.path = path.cgPath
}else {
CATransaction.begin()
CATransaction.setDisableActions(true)
gradientMaskLayer.path = path.cgPath
CATransaction.commit()
}
}
}

View File

@ -0,0 +1,81 @@
//
// JXSegmentedIndicatorImageView.swift
// JXSegmentedView
//
// Created by jiaxin on 2019/1/2.
// Copyright © 2019 jiaxin. All rights reserved.
//
import UIKit
open class JXSegmentedIndicatorImageView: JXSegmentedIndicatorBaseView {
open var image: UIImage? {
didSet {
layer.contents = image?.cgImage
}
}
open override func commonInit() {
super.commonInit()
indicatorWidth = 20
indicatorHeight = 20
layer.contentsGravity = .resizeAspect
}
open override func refreshIndicatorState(model: JXSegmentedIndicatorSelectedParams) {
super.refreshIndicatorState(model: model)
backgroundColor = nil
let width = getIndicatorWidth(itemFrame: model.currentSelectedItemFrame, itemContentWidth: model.currentItemContentWidth)
let height = getIndicatorHeight(itemFrame: model.currentSelectedItemFrame)
let x = model.currentSelectedItemFrame.origin.x + (model.currentSelectedItemFrame.size.width - width)/2
var y: CGFloat = 0
switch indicatorPosition {
case .top:
y = verticalOffset
case .bottom:
y = model.currentSelectedItemFrame.size.height - height - verticalOffset
case .center:
y = (model.currentSelectedItemFrame.size.height - height)/2 + verticalOffset
}
frame = CGRect(x: x, y: y, width: width, height: height)
}
open override func contentScrollViewDidScroll(model: JXSegmentedIndicatorTransitionParams) {
super.contentScrollViewDidScroll(model: model)
guard canHandleTransition(model: model) else {
return
}
let rightItemFrame = model.rightItemFrame
let leftItemFrame = model.leftItemFrame
let percent = model.percent
let targetWidth = getIndicatorWidth(itemFrame: model.leftItemFrame, itemContentWidth: model.leftItemContentWidth)
let leftX = leftItemFrame.origin.x + (leftItemFrame.size.width - targetWidth)/2
let rightX = rightItemFrame.origin.x + (rightItemFrame.size.width - targetWidth)/2
let targetX = JXSegmentedViewTool.interpolate(from: leftX, to: rightX, percent: CGFloat(percent))
self.frame.origin.x = targetX
}
open override func selectItem(model: JXSegmentedIndicatorSelectedParams) {
super.selectItem(model: model)
let targetWidth = getIndicatorWidth(itemFrame: model.currentSelectedItemFrame, itemContentWidth: model.currentItemContentWidth)
var toFrame = self.frame
toFrame.origin.x = model.currentSelectedItemFrame.origin.x + (model.currentSelectedItemFrame.size.width - targetWidth)/2
if canSelectedWithAnimation(model: model) {
UIView.animate(withDuration: scrollAnimationDuration, delay: 0, options: .curveEaseOut, animations: {
self.frame = toFrame
}) { (_) in
}
}else {
frame = toFrame
}
}
}

View File

@ -0,0 +1,116 @@
//
// JXSegmentedIndicatorLineView.swift
// JXSegmentedView
//
// Created by jiaxin on 2018/12/26.
// Copyright © 2018 jiaxin. All rights reserved.
//
import UIKit
public enum JXSegmentedIndicatorLineStyle {
case normal
case lengthen
case lengthenOffset
}
open class JXSegmentedIndicatorLineView: JXSegmentedIndicatorBaseView {
open var lineStyle: JXSegmentedIndicatorLineStyle = .normal
/// lineStylelengthenOffset使x
open var lineScrollOffsetX: CGFloat = 10
open override func commonInit() {
super.commonInit()
indicatorHeight = 3
}
open override func refreshIndicatorState(model: JXSegmentedIndicatorSelectedParams) {
super.refreshIndicatorState(model: model)
backgroundColor = indicatorColor
layer.cornerRadius = getIndicatorCornerRadius(itemFrame: model.currentSelectedItemFrame)
let width = getIndicatorWidth(itemFrame: model.currentSelectedItemFrame, itemContentWidth: model.currentItemContentWidth)
let height = getIndicatorHeight(itemFrame: model.currentSelectedItemFrame)
let x = model.currentSelectedItemFrame.origin.x + (model.currentSelectedItemFrame.size.width - width)/2
var y: CGFloat = 0
switch indicatorPosition {
case .top:
y = verticalOffset
case .bottom:
y = model.currentSelectedItemFrame.size.height - height - verticalOffset
case .center:
y = (model.currentSelectedItemFrame.size.height - height)/2 + verticalOffset
}
frame = CGRect(x: x, y: y, width: width, height: height)
}
open override func contentScrollViewDidScroll(model: JXSegmentedIndicatorTransitionParams) {
super.contentScrollViewDidScroll(model: model)
guard canHandleTransition(model: model) else {
return
}
let rightItemFrame = model.rightItemFrame
let leftItemFrame = model.leftItemFrame
let percent = model.percent
var targetX: CGFloat = leftItemFrame.origin.x
var targetWidth = getIndicatorWidth(itemFrame: leftItemFrame, itemContentWidth: model.leftItemContentWidth)
let leftWidth = targetWidth
let rightWidth = getIndicatorWidth(itemFrame: rightItemFrame, itemContentWidth: model.rightItemContentWidth)
let leftX = leftItemFrame.origin.x + (leftItemFrame.size.width - leftWidth)/2
let rightX = rightItemFrame.origin.x + (rightItemFrame.size.width - rightWidth)/2
switch lineStyle {
case .normal:
targetX = JXSegmentedViewTool.interpolate(from: leftX, to: rightX, percent: CGFloat(percent))
if indicatorWidth == JXSegmentedViewAutomaticDimension {
targetWidth = JXSegmentedViewTool.interpolate(from: leftWidth, to: rightWidth, percent: CGFloat(percent))
}
case .lengthen:
//50%width50%xwidth
let maxWidth = rightX - leftX + rightWidth
if percent <= 0.5 {
targetX = leftX
targetWidth = JXSegmentedViewTool.interpolate(from: leftWidth, to: maxWidth, percent: CGFloat(percent*2))
}else {
targetX = JXSegmentedViewTool.interpolate(from: leftX, to: rightX, percent: CGFloat((percent - 0.5)*2))
targetWidth = JXSegmentedViewTool.interpolate(from: maxWidth, to: rightWidth, percent: CGFloat((percent - 0.5)*2))
}
case .lengthenOffset:
//50%widthx50%xwidth
let maxWidth = rightX - leftX + rightWidth - lineScrollOffsetX*2
if percent <= 0.5 {
targetX = JXSegmentedViewTool.interpolate(from: leftX, to: leftX + lineScrollOffsetX, percent: CGFloat(percent*2))
targetWidth = JXSegmentedViewTool.interpolate(from: leftWidth, to: maxWidth, percent: CGFloat(percent*2))
}else {
targetX = JXSegmentedViewTool.interpolate(from:leftX + lineScrollOffsetX, to: rightX, percent: CGFloat((percent - 0.5)*2))
targetWidth = JXSegmentedViewTool.interpolate(from: maxWidth, to: rightWidth, percent: CGFloat((percent - 0.5)*2))
}
}
self.frame.origin.x = targetX
self.frame.size.width = targetWidth
}
open override func selectItem(model: JXSegmentedIndicatorSelectedParams) {
super.selectItem(model: model)
let targetWidth = getIndicatorWidth(itemFrame: model.currentSelectedItemFrame, itemContentWidth: model.currentItemContentWidth)
var toFrame = self.frame
toFrame.origin.x = model.currentSelectedItemFrame.origin.x + (model.currentSelectedItemFrame.size.width - targetWidth)/2
toFrame.size.width = targetWidth
if canSelectedWithAnimation(model: model) {
UIView.animate(withDuration: scrollAnimationDuration, delay: 0, options: .curveEaseOut, animations: {
self.frame = toFrame
}) { (_) in
}
}else {
frame = toFrame
}
}
}

View File

@ -0,0 +1,52 @@
//
// JXSegmentedIndicatorParamsModel.swift
// JXSegmentedView
//
// Created by jiaxin on 2018/12/26.
// Copyright © 2018 jiaxin. All rights reserved.
//
import Foundation
import UIKit
/**
api说明确认
model传递数据使model传递api新增参数api修改的地方就特别多了
*/
public struct JXSegmentedIndicatorSelectedParams {
public let currentSelectedIndex: Int
public let currentSelectedItemFrame: CGRect
public let selectedType: JXSegmentedViewItemSelectedType
public let currentItemContentWidth: CGFloat
/// collectionViewcontentSize
public var collectionViewContentSize: CGSize?
public init(currentSelectedIndex: Int, currentSelectedItemFrame: CGRect, selectedType: JXSegmentedViewItemSelectedType, currentItemContentWidth: CGFloat, collectionViewContentSize: CGSize?) {
self.currentSelectedIndex = currentSelectedIndex
self.currentSelectedItemFrame = currentSelectedItemFrame
self.selectedType = selectedType
self.currentItemContentWidth = currentItemContentWidth
self.collectionViewContentSize = collectionViewContentSize
}
}
public struct JXSegmentedIndicatorTransitionParams {
public let currentSelectedIndex: Int
public let leftIndex: Int
public let leftItemFrame: CGRect
public let rightIndex: Int
public let rightItemFrame: CGRect
public let leftItemContentWidth: CGFloat
public let rightItemContentWidth: CGFloat
public let percent: CGFloat
public init(currentSelectedIndex: Int, leftIndex: Int, leftItemFrame: CGRect, leftItemContentWidth: CGFloat, rightIndex: Int, rightItemFrame: CGRect, rightItemContentWidth: CGFloat, percent: CGFloat) {
self.currentSelectedIndex = currentSelectedIndex
self.leftIndex = leftIndex
self.leftItemFrame = leftItemFrame
self.leftItemContentWidth = leftItemContentWidth
self.rightIndex = rightIndex
self.rightItemFrame = rightItemFrame
self.rightItemContentWidth = rightItemContentWidth
self.percent = percent
}
}

View File

@ -0,0 +1,41 @@
//
// JXSegmentedIndicatorProtocol.swift
// JXSegmentedView
//
// Created by jiaxin on 2018/12/26.
// Copyright © 2018 jiaxin. All rights reserved.
//
import Foundation
import UIKit
public protocol JXSegmentedIndicatorProtocol: UIView {
/// indicatorframecellJXSegmentedTitleDataSourcedisTitleMaskEnabled使
/// indicatorindicatorisIndicatorConvertToItemFrameEnabledtrue
/// indicatorisIndicatorConvertToItemFrameEnabledtrueisIndicatorConvertToItemFrameEnabledtrueindicator
var isIndicatorConvertToItemFrameEnabled: Bool { get }
/// index
/// param selectedIndex index
/// param selectedCellFrame cellFrame
/// param contentSize collectionViewcontentSize
/// - Parameter model: model description
func refreshIndicatorState(model: JXSegmentedIndicatorSelectedParams)
/// contentScrollViewUI
/// param selectedIndex index
/// param leftIndex cellcellindex
/// param leftCellFrame cellcellframe
/// param rightIndex cellcellindex
/// param rightCellFrame cellcellframe
/// param percent
/// - Parameter model: model description
func contentScrollViewDidScroll(model: JXSegmentedIndicatorTransitionParams)
/// item
/// param selectedIndex index
/// param selectedCellFrame cellFrame
/// param selectedType
/// - Parameter model: model description
func selectItem(model: JXSegmentedIndicatorSelectedParams)
}

View File

@ -0,0 +1,39 @@
//
// JXSegmentedIndicatorRainbowLineView.swift
// JXSegmentedView
//
// Created by jiaxin on 2018/12/28.
// Copyright © 2018 jiaxin. All rights reserved.
//
import UIKit
/// indicatorColorindicatorColors
open class JXSegmentedIndicatorRainbowLineView: JXSegmentedIndicatorLineView {
/// itemsegmentedViewreloadData
open var indicatorColors = [UIColor]()
open override func refreshIndicatorState(model: JXSegmentedIndicatorSelectedParams) {
super.refreshIndicatorState(model: model)
backgroundColor = indicatorColors[model.currentSelectedIndex]
}
open override func contentScrollViewDidScroll(model: JXSegmentedIndicatorTransitionParams) {
super.contentScrollViewDidScroll(model: model)
guard canHandleTransition(model: model) else {
return
}
backgroundColor = JXSegmentedViewTool.interpolateColor(from: indicatorColors[model.leftIndex], to: indicatorColors[model.rightIndex], percent: model.percent)
}
open override func selectItem(model: JXSegmentedIndicatorSelectedParams) {
super.selectItem(model: model)
backgroundColor = indicatorColors[model.currentSelectedIndex]
}
}

View File

@ -0,0 +1,96 @@
//
// JXSegmentedIndicatorTriangleView.swift
// JXSegmentedView
//
// Created by jiaxin on 2018/12/28.
// Copyright © 2018 jiaxin. All rights reserved.
//
import UIKit
open class JXSegmentedIndicatorTriangleView: JXSegmentedIndicatorBaseView {
open override class var layerClass: AnyClass {
return CAShapeLayer.self
}
private var path = UIBezierPath()
open override func commonInit() {
super.commonInit()
indicatorWidth = 14
indicatorHeight = 10
}
open override func refreshIndicatorState(model: JXSegmentedIndicatorSelectedParams) {
super.refreshIndicatorState(model: model)
backgroundColor = nil
let shapeLayer = self.layer as! CAShapeLayer
shapeLayer.fillColor = indicatorColor.cgColor
let width = getIndicatorWidth(itemFrame: model.currentSelectedItemFrame, itemContentWidth: model.currentItemContentWidth)
let height = getIndicatorHeight(itemFrame: model.currentSelectedItemFrame)
let x = model.currentSelectedItemFrame.origin.x + (model.currentSelectedItemFrame.size.width - width)/2
var y: CGFloat = 0
switch indicatorPosition {
case .top:
y = verticalOffset
case .bottom:
y = model.currentSelectedItemFrame.size.height - height - verticalOffset
case .center:
y = (model.currentSelectedItemFrame.size.height - height)/2 + verticalOffset
}
frame = CGRect(x: x, y: y, width: width, height: height)
path = UIBezierPath()
if indicatorPosition == .bottom {
path.move(to: CGPoint(x: 0, y: height))
path.addLine(to: CGPoint(x: width/2, y: 0))
path.addLine(to: CGPoint(x: width, y: height))
}else {
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: width/2, y: height))
path.addLine(to: CGPoint(x: width, y: 0))
}
path.close()
shapeLayer.path = path.cgPath
}
open override func contentScrollViewDidScroll(model: JXSegmentedIndicatorTransitionParams) {
super.contentScrollViewDidScroll(model: model)
guard canHandleTransition(model: model) else {
return
}
let rightItemFrame = model.rightItemFrame
let leftItemFrame = model.leftItemFrame
let percent = model.percent
let targetWidth = getIndicatorWidth(itemFrame: model.leftItemFrame, itemContentWidth: model.leftItemContentWidth)
let leftX = leftItemFrame.origin.x + (leftItemFrame.size.width - targetWidth)/2
let rightX = rightItemFrame.origin.x + (rightItemFrame.size.width - targetWidth)/2
let targetX = JXSegmentedViewTool.interpolate(from: leftX, to: rightX, percent: CGFloat(percent))
self.frame.origin.x = targetX
}
open override func selectItem(model: JXSegmentedIndicatorSelectedParams) {
super.selectItem(model: model)
let targetWidth = getIndicatorWidth(itemFrame: model.currentSelectedItemFrame, itemContentWidth: model.currentItemContentWidth)
var toFrame = self.frame
toFrame.origin.x = model.currentSelectedItemFrame.origin.x + (model.currentSelectedItemFrame.size.width - targetWidth)/2
if canSelectedWithAnimation(model: model) {
UIView.animate(withDuration: scrollAnimationDuration, delay: 0, options: .curveEaseOut, animations: {
self.frame = toFrame
}) { (_) in
}
}else {
frame = toFrame
}
}
}

View File

@ -0,0 +1,52 @@
//
// JXSegmentedNumberCell.swift
// JXSegmentedView
//
// Created by jiaxin on 2018/12/28.
// Copyright © 2018 jiaxin. All rights reserved.
//
import UIKit
open class JXSegmentedNumberCell: JXSegmentedTitleCell {
public let numberLabel = UILabel()
open override func commonInit() {
super.commonInit()
numberLabel.isHidden = true
numberLabel.textAlignment = .center
numberLabel.layer.masksToBounds = true
contentView.addSubview(numberLabel)
}
open override func layoutSubviews() {
super.layoutSubviews()
guard let myItemModel = itemModel as? JXSegmentedNumberItemModel else {
return
}
numberLabel.sizeToFit()
let height = myItemModel.numberHeight
numberLabel.layer.cornerRadius = height/2
numberLabel.bounds.size = CGSize(width: numberLabel.bounds.size.width + myItemModel.numberWidthIncrement, height: height)
numberLabel.center = CGPoint(x: titleLabel.frame.maxX + myItemModel.numberOffset.x, y: titleLabel.frame.minY + myItemModel.numberOffset.y)
}
open override func reloadData(itemModel: JXSegmentedBaseItemModel, selectedType: JXSegmentedViewItemSelectedType) {
super.reloadData(itemModel: itemModel, selectedType: selectedType )
guard let myItemModel = itemModel as? JXSegmentedNumberItemModel else {
return
}
numberLabel.backgroundColor = myItemModel.numberBackgroundColor
numberLabel.textColor = myItemModel.numberTextColor
numberLabel.text = myItemModel.numberString
numberLabel.font = myItemModel.numberFont
numberLabel.isHidden = myItemModel.number == 0
setNeedsLayout()
}
}

View File

@ -0,0 +1,64 @@
//
// JXSegmentedNumberDataSource.swift
// JXSegmentedView
//
// Created by jiaxin on 2018/12/28.
// Copyright © 2018 jiaxin. All rights reserved.
//
import Foundation
import UIKit
open class JXSegmentedNumberDataSource: JXSegmentedTitleDataSource {
/// titlesitem0
open var numbers = [Int]()
/// numberLabelnumberLabel
open var numberWidthIncrement: CGFloat = 10
/// numberLabel
open var numberBackgroundColor: UIColor = .red
/// numberLabeltextColor
open var numberTextColor: UIColor = .white
/// numberLabelfont
open var numberFont: UIFont = UIFont.systemFont(ofSize: 11)
/// numberLabelcentertitleLabelnumberOffsetXY
open var numberOffset: CGPoint = CGPoint.zero
/// 999999+number
open var numberStringFormatterClosure: ((Int) -> String)?
/// numberLabel14
open var numberHeight: CGFloat = 14
open override func preferredItemModelInstance() -> JXSegmentedBaseItemModel {
return JXSegmentedNumberItemModel()
}
open override func preferredRefreshItemModel(_ itemModel: JXSegmentedBaseItemModel, at index: Int, selectedIndex: Int) {
super.preferredRefreshItemModel(itemModel, at: index, selectedIndex: selectedIndex)
guard let itemModel = itemModel as? JXSegmentedNumberItemModel else {
return
}
itemModel.number = numbers[index]
if numberStringFormatterClosure != nil {
itemModel.numberString = numberStringFormatterClosure!(itemModel.number)
}else {
itemModel.numberString = "\(itemModel.number)"
}
itemModel.numberTextColor = numberTextColor
itemModel.numberBackgroundColor = numberBackgroundColor
itemModel.numberOffset = numberOffset
itemModel.numberWidthIncrement = numberWidthIncrement
itemModel.numberHeight = numberHeight
itemModel.numberFont = numberFont
}
//MARK: - JXSegmentedViewDataSource
open override func registerCellClass(in segmentedView: JXSegmentedView) {
segmentedView.collectionView.register(JXSegmentedNumberCell.self, forCellWithReuseIdentifier: "cell")
}
open override func segmentedView(_ segmentedView: JXSegmentedView, cellForItemAt index: Int) -> JXSegmentedBaseCell {
let cell = segmentedView.dequeueReusableCell(withReuseIdentifier: "cell", at: index)
return cell
}
}

View File

@ -0,0 +1,21 @@
//
// JXSegmentedNumberItemModel.swift
// JXSegmentedView
//
// Created by jiaxin on 2018/12/28.
// Copyright © 2018 jiaxin. All rights reserved.
//
import Foundation
import UIKit
open class JXSegmentedNumberItemModel: JXSegmentedTitleItemModel {
open var number: Int = 0
open var numberString: String = "0"
open var numberBackgroundColor: UIColor = .red
open var numberTextColor: UIColor = .white
open var numberWidthIncrement: CGFloat = 0
open var numberFont: UIFont = UIFont.systemFont(ofSize: 11)
open var numberOffset: CGPoint = CGPoint.zero
open var numberHeight: CGFloat = 14
}

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>

View File

@ -0,0 +1,207 @@
//
// JXSegmentedTitleCell.swift
// JXSegmentedView
//
// Created by jiaxin on 2018/12/26.
// Copyright © 2018 jiaxin. All rights reserved.
//
import UIKit
open class JXSegmentedTitleCell: JXSegmentedBaseCell {
public let titleLabel = UILabel()
public let maskTitleLabel = UILabel()
public let titleMaskLayer = CALayer()
public let maskTitleMaskLayer = CALayer()
open override func commonInit() {
super.commonInit()
titleLabel.textAlignment = .center
contentView.addSubview(titleLabel)
maskTitleLabel.textAlignment = .center
maskTitleLabel.isHidden = true
contentView.addSubview(maskTitleLabel)
titleMaskLayer.backgroundColor = UIColor.red.cgColor
maskTitleMaskLayer.backgroundColor = UIColor.red.cgColor
maskTitleLabel.layer.mask = maskTitleMaskLayer
}
open override func layoutSubviews() {
super.layoutSubviews()
//使`sizeThatFits``sizeToFit`numberOfLines0cell`sizeToFit`labelsize`sizeThatFits`
let labelSize = titleLabel.sizeThatFits(self.contentView.bounds.size)
let labelBounds = CGRect(x: 0, y: 0, width: labelSize.width, height: labelSize.height)
titleLabel.bounds = labelBounds
titleLabel.center = contentView.center
maskTitleLabel.bounds = labelBounds
maskTitleLabel.center = contentView.center
}
open override func reloadData(itemModel: JXSegmentedBaseItemModel, selectedType: JXSegmentedViewItemSelectedType) {
super.reloadData(itemModel: itemModel, selectedType: selectedType )
guard let myItemModel = itemModel as? JXSegmentedTitleItemModel else {
return
}
titleLabel.numberOfLines = myItemModel.titleNumberOfLines
maskTitleLabel.numberOfLines = myItemModel.titleNumberOfLines
if myItemModel.isTitleZoomEnabled {
//fonttitleCurrentZoomScaletransform
let maxScaleFont = UIFont(descriptor: myItemModel.titleNormalFont.fontDescriptor, size: myItemModel.titleNormalFont.pointSize*CGFloat(myItemModel.titleSelectedZoomScale))
let baseScale = myItemModel.titleNormalFont.lineHeight/maxScaleFont.lineHeight
if myItemModel.isSelectedAnimable && canStartSelectedAnimation(itemModel: itemModel, selectedType: selectedType) {
//
let titleZoomClosure = preferredTitleZoomAnimateClosure(itemModel: myItemModel, baseScale: baseScale)
appendSelectedAnimationClosure(closure: titleZoomClosure)
}else {
titleLabel.font = maxScaleFont
maskTitleLabel.font = maxScaleFont
let currentTransform = CGAffineTransform(scaleX: baseScale*CGFloat(myItemModel.titleCurrentZoomScale), y: baseScale*CGFloat(myItemModel.titleCurrentZoomScale))
titleLabel.transform = currentTransform
maskTitleLabel.transform = currentTransform
}
}else {
if myItemModel.isSelected {
titleLabel.font = myItemModel.titleSelectedFont
maskTitleLabel.font = myItemModel.titleSelectedFont
}else {
titleLabel.font = myItemModel.titleNormalFont
maskTitleLabel.font = myItemModel.titleNormalFont
}
}
let title = myItemModel.title ?? ""
let attriText = NSMutableAttributedString(string: title)
if myItemModel.isTitleStrokeWidthEnabled {
if myItemModel.isSelectedAnimable && canStartSelectedAnimation(itemModel: itemModel, selectedType: selectedType) {
//
let titleStrokeWidthClosure = preferredTitleStrokeWidthAnimateClosure(itemModel: myItemModel, attriText: attriText)
appendSelectedAnimationClosure(closure: titleStrokeWidthClosure)
}else {
attriText.addAttributes([NSAttributedString.Key.strokeWidth: myItemModel.titleCurrentStrokeWidth], range: NSRange(location: 0, length: title.count))
titleLabel.attributedText = attriText
maskTitleLabel.attributedText = attriText
}
}else {
titleLabel.attributedText = attriText
maskTitleLabel.attributedText = attriText
}
if myItemModel.isTitleMaskEnabled {
//maskmaskTitleLabeltitleLabelmaskTitleLabeltitleSelectedColortitleLabeltitleNormalColor
//使titleMaskLayertitleLabelmaskTitleMaskLayermaskTitleLabel
maskTitleLabel.isHidden = false
titleLabel.textColor = myItemModel.titleNormalColor
maskTitleLabel.textColor = myItemModel.titleSelectedColor
let labelSize = maskTitleLabel.sizeThatFits(self.contentView.bounds.size)
let labelBounds = CGRect(x: 0, y: 0, width: labelSize.width, height: labelSize.height)
maskTitleLabel.bounds = labelBounds
var topMaskFrame = myItemModel.indicatorConvertToItemFrame
topMaskFrame.origin.y = 0
var bottomMaskFrame = topMaskFrame
var maskStartX: CGFloat = 0
if maskTitleLabel.bounds.size.width >= bounds.size.width {
topMaskFrame.origin.x -= (maskTitleLabel.bounds.size.width - bounds.size.width)/2
bottomMaskFrame.size.width = maskTitleLabel.bounds.size.width
maskStartX = -(maskTitleLabel.bounds.size.width - bounds.size.width)/2
}else {
topMaskFrame.origin.x -= (bounds.size.width - maskTitleLabel.bounds.size.width)/2
bottomMaskFrame.size.width = bounds.size.width
maskStartX = 0
}
bottomMaskFrame.origin.x = topMaskFrame.origin.x
if topMaskFrame.origin.x > maskStartX {
bottomMaskFrame.origin.x = topMaskFrame.origin.x - bottomMaskFrame.size.width
}else {
bottomMaskFrame.origin.x = topMaskFrame.maxX
}
CATransaction.begin()
CATransaction.setDisableActions(true)
if topMaskFrame.size.width > 0 && topMaskFrame.intersects(maskTitleLabel.frame) {
titleLabel.layer.mask = titleMaskLayer
titleMaskLayer.frame = bottomMaskFrame
maskTitleMaskLayer.frame = topMaskFrame
}else {
titleLabel.layer.mask = nil
maskTitleMaskLayer.frame = topMaskFrame
}
CATransaction.commit()
}else {
maskTitleLabel.isHidden = true
titleLabel.layer.mask = nil
if myItemModel.isSelectedAnimable && canStartSelectedAnimation(itemModel: itemModel, selectedType: selectedType) {
//
let titleColorClosure = preferredTitleColorAnimateClosure(itemModel: myItemModel)
appendSelectedAnimationClosure(closure: titleColorClosure)
}else {
titleLabel.textColor = myItemModel.titleCurrentColor
}
}
startSelectedAnimationIfNeeded(itemModel: itemModel, selectedType: selectedType)
setNeedsLayout()
}
open func preferredTitleZoomAnimateClosure(itemModel: JXSegmentedTitleItemModel, baseScale: CGFloat) -> JXSegmentedCellSelectedAnimationClosure {
return {[weak self] (percnet) in
if itemModel.isSelected {
//scale
itemModel.titleCurrentZoomScale = JXSegmentedViewTool.interpolate(from: itemModel.titleNormalZoomScale, to: itemModel.titleSelectedZoomScale, percent: percnet)
}else {
//scale
itemModel.titleCurrentZoomScale = JXSegmentedViewTool.interpolate(from: itemModel.titleSelectedZoomScale, to:itemModel.titleNormalZoomScale , percent: percnet)
}
let currentTransform = CGAffineTransform(scaleX: baseScale*itemModel.titleCurrentZoomScale, y: baseScale*itemModel.titleCurrentZoomScale)
self?.titleLabel.transform = currentTransform
self?.maskTitleLabel.transform = currentTransform
}
}
open func preferredTitleStrokeWidthAnimateClosure(itemModel: JXSegmentedTitleItemModel, attriText: NSMutableAttributedString) -> JXSegmentedCellSelectedAnimationClosure{
return {[weak self] (percent) in
if itemModel.isSelected {
//StrokeWidth
itemModel.titleCurrentStrokeWidth = JXSegmentedViewTool.interpolate(from: itemModel.titleNormalStrokeWidth, to: itemModel.titleSelectedStrokeWidth, percent: percent)
}else {
//StrokeWidth
itemModel.titleCurrentStrokeWidth = JXSegmentedViewTool.interpolate(from: itemModel.titleSelectedStrokeWidth, to:itemModel.titleNormalStrokeWidth , percent: percent)
}
attriText.addAttributes([NSAttributedString.Key.strokeWidth: itemModel.titleCurrentStrokeWidth], range: NSRange(location: 0, length: attriText.string.count))
self?.titleLabel.attributedText = attriText
self?.maskTitleLabel.attributedText = attriText
}
}
open func preferredTitleColorAnimateClosure(itemModel: JXSegmentedTitleItemModel) -> JXSegmentedCellSelectedAnimationClosure {
return {[weak self] (percent) in
if itemModel.isSelected {
//textColortitleNormalColortitleSelectedColor
itemModel.titleCurrentColor = JXSegmentedViewTool.interpolateThemeColor(from: itemModel.titleNormalColor, to: itemModel.titleSelectedColor, percent: percent)
}else {
//textColortitleSelectedColortitleNormalColor
itemModel.titleCurrentColor = JXSegmentedViewTool.interpolateThemeColor(from: itemModel.titleSelectedColor, to: itemModel.titleNormalColor, percent: percent)
}
self?.titleLabel.textColor = itemModel.titleCurrentColor
}
}
override func setSelectedStyle(isSelected: Bool) {
if isSelected {
self.titleLabel.textColor = (self.itemModel as? JXSegmentedTitleItemModel)?.titleSelectedColor
} else {
self.titleLabel.textColor = (self.itemModel as? JXSegmentedTitleItemModel)?.titleNormalColor
}
}
}

View File

@ -0,0 +1,202 @@
//
// JXSegmentedTitleView.swift
// JXSegmentedView
//
// Created by jiaxin on 2018/12/26.
// Copyright © 2018 jiaxin. All rights reserved.
//
import UIKit
open class JXSegmentedTitleDataSource: JXSegmentedBaseDataSource {
/// title
open var titles = [String]()
/// indexcell
open var configuration: JXSegmentedTitleDynamicConfiguration?
/// JXSegmentedViewUITableViewcellJXSegmentedViewreloadDatatitleUITableViewcellModeltitlesJXSegmentedView
open var widthForTitleClosure: ((String)->(CGFloat))?
/// labelnumberOfLines
open var titleNumberOfLines: Int = 1
/// titletextColor
open var titleNormalColor: UIColor = .black
/// titletextColor
open var titleSelectedColor: UIColor = .red
/// title
open var titleNormalFont: UIFont = UIFont.systemFont(ofSize: 15)
/// titletitleNormalFont
open var titleSelectedFont: UIFont?
/// title
open var isTitleColorGradientEnabled: Bool = false
/// title使titleNormalFonttitleSelectedFont
open var isTitleZoomEnabled: Bool = false
/// isTitleZoomEnabledtruetitleNormalFontpointSize1010*1.2=12
open var titleSelectedZoomScale: CGFloat = 1.2
/// title线使titleNormalFonttitleSelectedFont
open var isTitleStrokeWidthEnabled: Bool = false
/// NSStrokeWidthAttributeName
open var titleSelectedStrokeWidth: CGFloat = -2
/// title使
open var isTitleMaskEnabled: Bool = false
open override func preferredItemCount() -> Int {
return titles.count
}
open override func preferredItemModelInstance() -> JXSegmentedBaseItemModel {
return JXSegmentedTitleItemModel()
}
open override func preferredRefreshItemModel( _ itemModel: JXSegmentedBaseItemModel, at index: Int, selectedIndex: Int) {
super.preferredRefreshItemModel(itemModel, at: index, selectedIndex: selectedIndex)
guard let myItemModel = itemModel as? JXSegmentedTitleItemModel else {
return
}
myItemModel.title = titles[index]
myItemModel.textWidth = widthForTitle(myItemModel.title ?? "", index)
myItemModel.titleNumberOfLines = innerTitleNumberOfLines(at: index)
myItemModel.isSelectedAnimable = isSelectedAnimable
myItemModel.titleNormalColor = innerTitleNormalColor(at: index)
myItemModel.titleSelectedColor = innerTitleSelectedColor(at: index)
myItemModel.titleNormalFont = innerTitleNormalFont(at: index)
if let selectedFont = innerTitleSelectedFont(at: index) {
myItemModel.titleSelectedFont = selectedFont
} else {
myItemModel.titleSelectedFont = innerTitleNormalFont(at: index)
}
myItemModel.isTitleZoomEnabled = isTitleZoomEnabled
myItemModel.isTitleStrokeWidthEnabled = isTitleStrokeWidthEnabled
myItemModel.isTitleMaskEnabled = isTitleMaskEnabled
myItemModel.titleNormalZoomScale = 1
myItemModel.titleSelectedZoomScale = titleSelectedZoomScale
myItemModel.titleSelectedStrokeWidth = titleSelectedStrokeWidth
myItemModel.titleNormalStrokeWidth = 0
if index == selectedIndex {
myItemModel.titleCurrentColor = innerTitleSelectedColor(at: index)
myItemModel.titleCurrentZoomScale = titleSelectedZoomScale
myItemModel.titleCurrentStrokeWidth = titleSelectedStrokeWidth
}else {
myItemModel.titleCurrentColor = innerTitleNormalColor(at: index)
myItemModel.titleCurrentZoomScale = 1
myItemModel.titleCurrentStrokeWidth = 0
}
}
open func widthForTitle(_ title: String, _ index: Int) -> CGFloat {
if widthForTitleClosure != nil {
return widthForTitleClosure!(title)
}else {
let textWidth = NSString(string: title).boundingRect(with: CGSize(width: CGFloat.infinity, height: CGFloat.infinity), options: [.usesFontLeading, .usesLineFragmentOrigin], attributes: [NSAttributedString.Key.font : innerTitleNormalFont(at: index)], context: nil).size.width
return CGFloat(ceilf(Float(textWidth)))
}
}
/// `preferredRefreshItemModel( _ itemModel: JXSegmentedBaseItemModel, at index: Int, selectedIndex: Int)`使
open override func preferredSegmentedView(_ segmentedView: JXSegmentedView, widthForItemAt index: Int) -> CGFloat {
var width = super.preferredSegmentedView(segmentedView, widthForItemAt: index)
if itemWidth == JXSegmentedViewAutomaticDimension {
width += (dataSource[index] as! JXSegmentedTitleItemModel).textWidth
}else {
width += itemWidth
}
return width
}
//MARK: - JXSegmentedViewDataSource
open override func registerCellClass(in segmentedView: JXSegmentedView) {
segmentedView.collectionView.register(JXSegmentedTitleCell.self, forCellWithReuseIdentifier: "cell")
}
open override func segmentedView(_ segmentedView: JXSegmentedView, cellForItemAt index: Int) -> JXSegmentedBaseCell {
let cell = segmentedView.dequeueReusableCell(withReuseIdentifier: "cell", at: index)
return cell
}
public override func segmentedView(_ segmentedView: JXSegmentedView, widthForItemContentAt index: Int) -> CGFloat {
let model = dataSource[index] as! JXSegmentedTitleItemModel
if isTitleZoomEnabled {
return model.textWidth*model.titleCurrentZoomScale
}else {
return model.textWidth
}
}
open override func refreshItemModel(_ segmentedView: JXSegmentedView, leftItemModel: JXSegmentedBaseItemModel, rightItemModel: JXSegmentedBaseItemModel, percent: CGFloat) {
super.refreshItemModel(segmentedView, leftItemModel: leftItemModel, rightItemModel: rightItemModel, percent: percent)
guard let leftModel = leftItemModel as? JXSegmentedTitleItemModel, let rightModel = rightItemModel as? JXSegmentedTitleItemModel else {
return
}
if isTitleZoomEnabled && isItemTransitionEnabled {
leftModel.titleCurrentZoomScale = JXSegmentedViewTool.interpolate(from: leftModel.titleSelectedZoomScale, to: leftModel.titleNormalZoomScale, percent: CGFloat(percent))
rightModel.titleCurrentZoomScale = JXSegmentedViewTool.interpolate(from: rightModel.titleNormalZoomScale, to: rightModel.titleSelectedZoomScale, percent: CGFloat(percent))
}
if isTitleStrokeWidthEnabled && isItemTransitionEnabled {
leftModel.titleCurrentStrokeWidth = JXSegmentedViewTool.interpolate(from: leftModel.titleSelectedStrokeWidth, to: leftModel.titleNormalStrokeWidth, percent: CGFloat(percent))
rightModel.titleCurrentStrokeWidth = JXSegmentedViewTool.interpolate(from: rightModel.titleNormalStrokeWidth, to: rightModel.titleSelectedStrokeWidth, percent: CGFloat(percent))
}
if isTitleColorGradientEnabled && isItemTransitionEnabled {
leftModel.titleCurrentColor = JXSegmentedViewTool.interpolateThemeColor(from: leftModel.titleSelectedColor, to: leftModel.titleNormalColor, percent: percent)
rightModel.titleCurrentColor = JXSegmentedViewTool.interpolateThemeColor(from:rightModel.titleNormalColor , to:rightModel.titleSelectedColor, percent: percent)
}
}
open override func refreshItemModel(_ segmentedView: JXSegmentedView, currentSelectedItemModel: JXSegmentedBaseItemModel, willSelectedItemModel: JXSegmentedBaseItemModel, selectedType: JXSegmentedViewItemSelectedType) {
super.refreshItemModel(segmentedView, currentSelectedItemModel: currentSelectedItemModel, willSelectedItemModel: willSelectedItemModel, selectedType: selectedType)
guard let myCurrentSelectedItemModel = currentSelectedItemModel as? JXSegmentedTitleItemModel, let myWillSelectedItemModel = willSelectedItemModel as? JXSegmentedTitleItemModel else {
return
}
myCurrentSelectedItemModel.titleCurrentColor = myCurrentSelectedItemModel.titleNormalColor
myCurrentSelectedItemModel.titleCurrentZoomScale = myCurrentSelectedItemModel.titleNormalZoomScale
myCurrentSelectedItemModel.titleCurrentStrokeWidth = myCurrentSelectedItemModel.titleNormalStrokeWidth
myCurrentSelectedItemModel.indicatorConvertToItemFrame = CGRect.zero
myWillSelectedItemModel.titleCurrentColor = myWillSelectedItemModel.titleSelectedColor
myWillSelectedItemModel.titleCurrentZoomScale = myWillSelectedItemModel.titleSelectedZoomScale
myWillSelectedItemModel.titleCurrentStrokeWidth = myWillSelectedItemModel.titleSelectedStrokeWidth
}
// MARK: - Configuration
private func innerTitleNumberOfLines(at index: Int) -> Int {
if let configuration {
return configuration.titleNumberOfLines(at: index)
} else {
return titleNumberOfLines
}
}
private func innerTitleNormalColor(at index: Int) -> UIColor {
if let configuration {
return configuration.titleNormalColor(at: index)
} else {
return titleNormalColor
}
}
private func innerTitleSelectedColor(at index: Int) -> UIColor {
if let configuration {
return configuration.titleSelectedColor(at: index)
} else {
return titleSelectedColor
}
}
private func innerTitleNormalFont(at index: Int) -> UIFont {
if let configuration {
return configuration.titleNormalFont(at: index)
} else {
return titleNormalFont
}
}
private func innerTitleSelectedFont(at index: Int) -> UIFont? {
if let configuration {
return configuration.titleSelectedFont(at: index)
} else {
return titleSelectedFont
}
}
}

View File

@ -0,0 +1,17 @@
//
// JXSegmentedTitleBaseDataSource.swift
// JXSegmentedView
//
// Created by Jiaxin Pu on 2024/4/16.
// Copyright © 2024 jiaxin. All rights reserved.
//
import UIKit
public protocol JXSegmentedTitleDynamicConfiguration {
func titleNumberOfLines(at index: Int) -> Int
func titleNormalColor(at index: Int) -> UIColor
func titleSelectedColor(at index: Int) -> UIColor
func titleNormalFont(at index: Int) -> UIFont
func titleSelectedFont(at index: Int) -> UIFont?
}

View File

@ -0,0 +1,29 @@
//
// JXSegmentedTitleItemModel.swift
// JXSegmentedView
//
// Created by jiaxin on 2018/12/26.
// Copyright © 2018 jiaxin. All rights reserved.
//
import UIKit
open class JXSegmentedTitleItemModel: JXSegmentedBaseItemModel {
open var title: String?
open var titleNumberOfLines: Int = 0
open var titleNormalColor: UIColor = .black
open var titleCurrentColor: UIColor = .black
open var titleSelectedColor: UIColor = .red
open var titleNormalFont: UIFont = UIFont.systemFont(ofSize: 15)
open var titleSelectedFont: UIFont = UIFont.systemFont(ofSize: 15)
open var isTitleZoomEnabled: Bool = false
open var titleNormalZoomScale: CGFloat = 0
open var titleCurrentZoomScale: CGFloat = 0
open var titleSelectedZoomScale: CGFloat = 0
open var isTitleStrokeWidthEnabled: Bool = false
open var titleNormalStrokeWidth: CGFloat = 0
open var titleCurrentStrokeWidth: CGFloat = 0
open var titleSelectedStrokeWidth: CGFloat = 0
open var isTitleMaskEnabled: Bool = false
open var textWidth: CGFloat = 0
}

View File

@ -0,0 +1,76 @@
//
// JXSegmentedTitleGradientCell.swift
// JXSegmentedView
//
// Created by jiaxin on 2019/1/23.
// Copyright © 2019 jiaxin. All rights reserved.
//
import UIKit
open class JXSegmentedTitleGradientCell: JXSegmentedTitleCell {
public let gradientLayer = CAGradientLayer()
private var canStartSelectedAnimation: Bool = false
open override func commonInit() {
super.commonInit()
titleLabel.removeFromSuperview()
maskTitleLabel.removeFromSuperview()
gradientLayer.colors = [UIColor.red.cgColor, UIColor.yellow.cgColor, UIColor.green.cgColor]
contentView.layer.addSublayer(gradientLayer)
gradientLayer.mask = titleLabel.layer
}
open override func layoutSubviews() {
super.layoutSubviews()
CATransaction.begin()
CATransaction.setDisableActions(true)
gradientLayer.frame = titleLabel.frame
CATransaction.commit()
titleLabel.frame = gradientLayer.bounds
}
open override func reloadData(itemModel: JXSegmentedBaseItemModel, selectedType: JXSegmentedViewItemSelectedType) {
super.reloadData(itemModel: itemModel, selectedType: selectedType)
guard let myItemModel = itemModel as? JXSegmentedTitleGradientItemModel else {
return
}
if myItemModel.isSelectedAnimable && canStartSelectedAnimation(itemModel: myItemModel, selectedType: selectedType) {
let closure: JXSegmentedCellSelectedAnimationClosure = {[weak self] (percent) in
if myItemModel.isSelected {
myItemModel.titleCurrentGradientColors = JXSegmentedViewTool.interpolateColors(from: myItemModel.titleNormalGradientColors, to: myItemModel.titleSelectedGradientColors, percent: percent)
}else {
myItemModel.titleCurrentGradientColors = JXSegmentedViewTool.interpolateColors(from: myItemModel.titleSelectedGradientColors, to: myItemModel.titleNormalGradientColors, percent: percent)
}
CATransaction.begin()
CATransaction.setDisableActions(true)
self?.gradientLayer.colors = myItemModel.titleCurrentGradientColors
CATransaction.commit()
self?.setNeedsLayout()
self?.layoutIfNeeded()
}
appendSelectedAnimationClosure(closure: closure)
canStartSelectedAnimation = true
startSelectedAnimationIfNeeded(itemModel: myItemModel, selectedType: selectedType)
canStartSelectedAnimation = false
}else {
CATransaction.begin()
CATransaction.setDisableActions(true)
gradientLayer.startPoint = myItemModel.titleGradientStartPoint
gradientLayer.endPoint = myItemModel.titleGradientEndPoint
gradientLayer.colors = myItemModel.titleCurrentGradientColors
CATransaction.commit()
}
}
open override func startSelectedAnimationIfNeeded(itemModel: JXSegmentedBaseItemModel, selectedType: JXSegmentedViewItemSelectedType) {
if canStartSelectedAnimation {
super.startSelectedAnimationIfNeeded(itemModel: itemModel, selectedType: selectedType)
}
}
}

View File

@ -0,0 +1,76 @@
//
// JXSegmentedTitleGradientDataSource.swift
// JXSegmentedView
//
// Created by jiaxin on 2019/1/23.
// Copyright © 2019 jiaxin. All rights reserved.
//
import UIKit
open class JXSegmentedTitleGradientDataSource: JXSegmentedTitleDataSource {
/// titlecolors
open var titleNormalGradientColors: [CGColor] = [UIColor.black.cgColor, UIColor.black.cgColor, UIColor.black.cgColor]
/// titlecolors
open var titleSelectedGradientColors: [CGColor] = [UIColor(red: 18/255.0, green: 194/255.0, blue: 233/255.0, alpha: 1).cgColor, UIColor(red: 196/255.0, green: 113/255.0, blue: 237/255.0, alpha: 1).cgColor, UIColor(red: 246/255.0, green: 79/255.0, blue: 89/255.0, alpha: 1).cgColor]
/// titleStartPoint
open var titleGradientStartPoint: CGPoint = CGPoint(x: 0, y: 0)
/// titleEndPoint
open var titleGradientEndPoint: CGPoint = CGPoint(x: 1, y: 0)
open override func preferredItemModelInstance() -> JXSegmentedBaseItemModel {
return JXSegmentedTitleGradientItemModel()
}
open override func preferredRefreshItemModel(_ itemModel: JXSegmentedBaseItemModel, at index: Int, selectedIndex: Int) {
super.preferredRefreshItemModel(itemModel, at: index, selectedIndex: selectedIndex)
guard let itemModel = itemModel as? JXSegmentedTitleGradientItemModel else {
return
}
itemModel.titleGradientStartPoint = titleGradientStartPoint
itemModel.titleGradientEndPoint = titleGradientEndPoint
itemModel.titleNormalGradientColors = titleNormalGradientColors
itemModel.titleSelectedGradientColors = titleSelectedGradientColors
if index == selectedIndex {
itemModel.titleCurrentGradientColors = itemModel.titleSelectedGradientColors
}else {
itemModel.titleCurrentGradientColors = itemModel.titleNormalGradientColors
}
}
//MARK: - JXSegmentedViewDataSource
open override func registerCellClass(in segmentedView: JXSegmentedView) {
segmentedView.collectionView.register(JXSegmentedTitleGradientCell.self, forCellWithReuseIdentifier: "cell")
}
open override func segmentedView(_ segmentedView: JXSegmentedView, cellForItemAt index: Int) -> JXSegmentedBaseCell {
let cell = segmentedView.dequeueReusableCell(withReuseIdentifier: "cell", at: index)
return cell
}
open override func refreshItemModel(_ segmentedView: JXSegmentedView, leftItemModel: JXSegmentedBaseItemModel, rightItemModel: JXSegmentedBaseItemModel, percent: CGFloat) {
super.refreshItemModel(segmentedView, leftItemModel: leftItemModel, rightItemModel: rightItemModel, percent: percent)
guard let leftModel = leftItemModel as? JXSegmentedTitleGradientItemModel, let rightModel = rightItemModel as? JXSegmentedTitleGradientItemModel else {
return
}
if isTitleColorGradientEnabled && isItemTransitionEnabled {
leftModel.titleCurrentGradientColors = JXSegmentedViewTool.interpolateColors(from: leftModel.titleSelectedGradientColors, to: leftModel.titleNormalGradientColors, percent: percent)
rightModel.titleCurrentGradientColors = JXSegmentedViewTool.interpolateColors(from: rightModel.titleNormalGradientColors, to: rightModel.titleSelectedGradientColors, percent: percent)
}
}
open override func refreshItemModel(_ segmentedView: JXSegmentedView, currentSelectedItemModel: JXSegmentedBaseItemModel, willSelectedItemModel: JXSegmentedBaseItemModel, selectedType: JXSegmentedViewItemSelectedType) {
super.refreshItemModel(segmentedView, currentSelectedItemModel: currentSelectedItemModel, willSelectedItemModel: willSelectedItemModel, selectedType: selectedType)
guard let myCurrentSelectedItemModel = currentSelectedItemModel as? JXSegmentedTitleGradientItemModel, let myWillSelectedItemModel = willSelectedItemModel as? JXSegmentedTitleGradientItemModel else {
return
}
myCurrentSelectedItemModel.titleCurrentGradientColors = myCurrentSelectedItemModel.titleNormalGradientColors
myWillSelectedItemModel.titleCurrentGradientColors = myWillSelectedItemModel.titleSelectedGradientColors
}
}

View File

@ -0,0 +1,17 @@
//
// JXSegmentedTitleGradientItemModel.swift
// JXSegmentedView
//
// Created by jiaxin on 2019/1/23.
// Copyright © 2019 jiaxin. All rights reserved.
//
import UIKit
open class JXSegmentedTitleGradientItemModel: JXSegmentedTitleItemModel {
open var titleNormalGradientColors: [CGColor] = [CGColor]()
open var titleCurrentGradientColors: [CGColor] = [CGColor]()
open var titleSelectedGradientColors: [CGColor] = [CGColor]()
open var titleGradientStartPoint: CGPoint = CGPoint.zero
open var titleGradientEndPoint: CGPoint = CGPoint.zero
}

View File

@ -0,0 +1,100 @@
//
// JXSegmentedTitleImageCell.swift
// JXSegmentedView
//
// Created by jiaxin on 2018/12/29.
// Copyright © 2018 jiaxin. All rights reserved.
//
import UIKit
open class JXSegmentedTitleImageCell: JXSegmentedTitleCell {
public let imageView = UIImageView()
private var currentImageInfo: String?
open override func prepareForReuse() {
super.prepareForReuse()
currentImageInfo = nil
}
open override func commonInit() {
super.commonInit()
imageView.contentMode = .scaleAspectFit
contentView.addSubview(imageView)
}
open override func layoutSubviews() {
super.layoutSubviews()
guard let myItemModel = itemModel as? JXSegmentedTitleImageItemModel else {
return
}
let imageSize = myItemModel.imageSize
switch myItemModel.titleImageType {
case .topImage:
let contentHeight = imageSize.height + myItemModel.titleImageSpacing + titleLabel.bounds.size.height
imageView.center = CGPoint(x: contentView.bounds.size.width/2, y: (contentView.bounds.size.height - contentHeight)/2 + imageSize.height/2)
titleLabel.center = CGPoint(x: contentView.bounds.size.width/2, y: imageView.frame.maxY + myItemModel.titleImageSpacing + titleLabel.bounds.size.height/2)
case .leftImage:
let contentWidth = imageSize.width + myItemModel.titleImageSpacing + titleLabel.bounds.size.width
imageView.center = CGPoint(x: (contentView.bounds.size.width - contentWidth)/2 + imageSize.width/2, y: contentView.bounds.size.height/2)
titleLabel.center = CGPoint(x: imageView.frame.maxX + myItemModel.titleImageSpacing + titleLabel.bounds.size.width/2, y: contentView.bounds.size.height/2)
case .bottomImage:
let contentHeight = imageSize.height + myItemModel.titleImageSpacing + titleLabel.bounds.size.height
titleLabel.center = CGPoint(x: contentView.bounds.size.width/2, y: (contentView.bounds.size.height - contentHeight)/2 + titleLabel.bounds.size.height/2)
imageView.center = CGPoint(x: contentView.bounds.size.width/2, y: titleLabel.frame.maxY + myItemModel.titleImageSpacing + imageSize.height/2)
case .rightImage:
let contentWidth = imageSize.width + myItemModel.titleImageSpacing + titleLabel.bounds.size.width
titleLabel.center = CGPoint(x: (contentView.bounds.size.width - contentWidth)/2 + titleLabel.bounds.size.width/2, y: contentView.bounds.size.height/2)
imageView.center = CGPoint(x: titleLabel.frame.maxX + myItemModel.titleImageSpacing + imageSize.width/2, y: contentView.bounds.size.height/2)
case .onlyImage:
imageView.center = CGPoint(x: contentView.bounds.size.width/2, y: contentView.bounds.size.height/2)
case .onlyTitle:
titleLabel.center = CGPoint(x: contentView.bounds.size.width/2, y: contentView.bounds.size.height/2)
}
}
open override func reloadData(itemModel: JXSegmentedBaseItemModel, selectedType: JXSegmentedViewItemSelectedType) {
super.reloadData(itemModel: itemModel, selectedType: selectedType )
guard let myItemModel = itemModel as? JXSegmentedTitleImageItemModel else {
return
}
titleLabel.isHidden = false
imageView.isHidden = false
if myItemModel.titleImageType == .onlyTitle {
imageView.isHidden = true
}else if myItemModel.titleImageType == .onlyImage {
titleLabel.isHidden = true
}
imageView.bounds = CGRect(x: 0, y: 0, width: myItemModel.imageSize.width, height: myItemModel.imageSize.height)
var normalImageInfo = myItemModel.normalImageInfo
if myItemModel.isSelected && myItemModel.selectedImageInfo != nil {
normalImageInfo = myItemModel.selectedImageInfo
}
//`func reloadData(itemModel: JXSegmentedBaseItemModel, selectedType: JXSegmentedViewItemSelectedType)`
if normalImageInfo != nil && normalImageInfo != currentImageInfo {
currentImageInfo = normalImageInfo
if myItemModel.loadImageClosure != nil {
myItemModel.loadImageClosure!(imageView, normalImageInfo!)
}else {
imageView.image = UIImage(named: normalImageInfo!)
}
}
if myItemModel.isImageZoomEnabled {
imageView.transform = CGAffineTransform(scaleX: myItemModel.imageCurrentZoomScale, y: myItemModel.imageCurrentZoomScale)
}else {
imageView.transform = .identity
}
setNeedsLayout()
}
}

View File

@ -0,0 +1,130 @@
//
// JXSegmentedTitleImageDataSource.swift
// JXSegmentedView
//
// Created by jiaxin on 2018/12/29.
// Copyright © 2018 jiaxin. All rights reserved.
//
import UIKit
public enum JXSegmentedTitleImageType {
case topImage
case leftImage
case bottomImage
case rightImage
case onlyImage
case onlyTitle
}
public typealias LoadImageClosure = ((UIImageView, String) -> Void)
open class JXSegmentedTitleImageDataSource: JXSegmentedTitleDataSource {
open var titleImageType: JXSegmentedTitleImageType = .rightImage
/// itemImageName
open var normalImageInfos: [String]?
/// itemImageName
open var selectedImageInfos: [String]?
/// UIImage(named:)
open var loadImageClosure: LoadImageClosure?
///
open var imageSize: CGSize = CGSize(width: 20, height: 20)
/// titleimage
open var titleImageSpacing: CGFloat = 5
///
open var isImageZoomEnabled: Bool = false
/// scale
open var imageSelectedZoomScale: CGFloat = 1.2
open override func preferredItemModelInstance() -> JXSegmentedBaseItemModel {
return JXSegmentedTitleImageItemModel()
}
open override func preferredRefreshItemModel(_ itemModel: JXSegmentedBaseItemModel, at index: Int, selectedIndex: Int) {
super.preferredRefreshItemModel(itemModel, at: index, selectedIndex: selectedIndex)
guard let itemModel = itemModel as? JXSegmentedTitleImageItemModel else {
return
}
itemModel.titleImageType = titleImageType
itemModel.normalImageInfo = normalImageInfos?[index]
itemModel.selectedImageInfo = selectedImageInfos?[index]
itemModel.loadImageClosure = loadImageClosure
itemModel.imageSize = imageSize
itemModel.isImageZoomEnabled = isImageZoomEnabled
itemModel.imageNormalZoomScale = 1
itemModel.imageSelectedZoomScale = imageSelectedZoomScale
itemModel.titleImageSpacing = titleImageSpacing
if index == selectedIndex {
itemModel.imageCurrentZoomScale = itemModel.imageSelectedZoomScale
}else {
itemModel.imageCurrentZoomScale = itemModel.imageNormalZoomScale
}
}
open override func preferredSegmentedView(_ segmentedView: JXSegmentedView, widthForItemAt index: Int) -> CGFloat {
var width = super.preferredSegmentedView(segmentedView, widthForItemAt: index)
if itemWidth == JXSegmentedViewAutomaticDimension {
switch titleImageType {
case .leftImage, .rightImage:
width += titleImageSpacing + imageSize.width
case .topImage, .bottomImage:
width = max(itemWidth, imageSize.width)
case .onlyImage:
width = imageSize.width
case .onlyTitle:
break
}
}
return width
}
public override func segmentedView(_ segmentedView: JXSegmentedView, widthForItemContentAt index: Int) -> CGFloat {
var width = super.segmentedView(segmentedView, widthForItemContentAt: index)
switch titleImageType {
case .leftImage, .rightImage:
width += titleImageSpacing + imageSize.width
case .topImage, .bottomImage:
width = max(itemWidth, imageSize.width)
case .onlyImage:
width = imageSize.width
case .onlyTitle:
break
}
return width
}
//MARK: - JXSegmentedViewDataSource
open override func registerCellClass(in segmentedView: JXSegmentedView) {
segmentedView.collectionView.register(JXSegmentedTitleImageCell.self, forCellWithReuseIdentifier: "cell")
}
open override func segmentedView(_ segmentedView: JXSegmentedView, cellForItemAt index: Int) -> JXSegmentedBaseCell {
let cell = segmentedView.dequeueReusableCell(withReuseIdentifier: "cell", at: index)
return cell
}
open override func refreshItemModel(_ segmentedView: JXSegmentedView, leftItemModel: JXSegmentedBaseItemModel, rightItemModel: JXSegmentedBaseItemModel, percent: CGFloat) {
super.refreshItemModel(segmentedView, leftItemModel: leftItemModel, rightItemModel: rightItemModel, percent: percent)
guard let leftModel = leftItemModel as? JXSegmentedTitleImageItemModel, let rightModel = rightItemModel as? JXSegmentedTitleImageItemModel else {
return
}
if isImageZoomEnabled && isItemTransitionEnabled {
leftModel.imageCurrentZoomScale = JXSegmentedViewTool.interpolate(from: imageSelectedZoomScale, to: 1, percent: CGFloat(percent))
rightModel.imageCurrentZoomScale = JXSegmentedViewTool.interpolate(from: 1, to: imageSelectedZoomScale, percent: CGFloat(percent))
}
}
open override func refreshItemModel(_ segmentedView: JXSegmentedView, currentSelectedItemModel: JXSegmentedBaseItemModel, willSelectedItemModel: JXSegmentedBaseItemModel, selectedType: JXSegmentedViewItemSelectedType) {
super.refreshItemModel(segmentedView, currentSelectedItemModel: currentSelectedItemModel, willSelectedItemModel: willSelectedItemModel, selectedType: selectedType)
guard let myCurrentSelectedItemModel = currentSelectedItemModel as? JXSegmentedTitleImageItemModel, let myWillSelectedItemModel = willSelectedItemModel as? JXSegmentedTitleImageItemModel else {
return
}
myCurrentSelectedItemModel.imageCurrentZoomScale = myCurrentSelectedItemModel.imageNormalZoomScale
myWillSelectedItemModel.imageCurrentZoomScale = myWillSelectedItemModel.imageSelectedZoomScale
}
}

View File

@ -0,0 +1,22 @@
//
// JXSegmentedTitleImageItemModel.swift
// JXSegmentedView
//
// Created by jiaxin on 2018/12/29.
// Copyright © 2018 jiaxin. All rights reserved.
//
import UIKit
open class JXSegmentedTitleImageItemModel: JXSegmentedTitleItemModel {
open var titleImageType: JXSegmentedTitleImageType = .rightImage
open var normalImageInfo: String?
open var selectedImageInfo: String?
open var loadImageClosure: LoadImageClosure?
open var imageSize: CGSize = CGSize.zero
open var titleImageSpacing: CGFloat = 0
open var isImageZoomEnabled: Bool = false
open var imageNormalZoomScale: CGFloat = 0
open var imageCurrentZoomScale: CGFloat = 0
open var imageSelectedZoomScale: CGFloat = 0
}

View File

@ -0,0 +1,141 @@
//
// JXSegmentedTitleOrImageCell.swift
// JXSegmentedView
//
// Created by jiaxin on 2019/1/22.
// Copyright © 2019 jiaxin. All rights reserved.
//
import UIKit
open class JXSegmentedTitleOrImageCell: JXSegmentedTitleCell {
public let imageView = UIImageView()
private var currentImageInfo: String?
open override func prepareForReuse() {
super.prepareForReuse()
currentImageInfo = nil
}
open override func commonInit() {
super.commonInit()
imageView.contentMode = .scaleAspectFit
contentView.addSubview(imageView)
}
open override func layoutSubviews() {
super.layoutSubviews()
imageView.center = contentView.center
}
open override func reloadData(itemModel: JXSegmentedBaseItemModel, selectedType: JXSegmentedViewItemSelectedType) {
super.reloadData(itemModel: itemModel, selectedType: selectedType )
guard let myItemModel = itemModel as? JXSegmentedTitleOrImageItemModel else {
return
}
if myItemModel.isSelected && myItemModel.selectedImageInfo != nil {
titleLabel.isHidden = true
imageView.isHidden = false
}else {
titleLabel.isHidden = false
imageView.isHidden = true
}
imageView.bounds = CGRect(x: 0, y: 0, width: myItemModel.imageSize.width, height: myItemModel.imageSize.height)
//`func reloadData(itemModel: JXSegmentedBaseItemModel, selectedType: JXSegmentedViewItemSelectedType)`
if myItemModel.isSelected &&
myItemModel.selectedImageInfo != nil &&
myItemModel.selectedImageInfo != currentImageInfo {
currentImageInfo = myItemModel.selectedImageInfo
if myItemModel.loadImageClosure != nil {
myItemModel.loadImageClosure!(imageView, myItemModel.selectedImageInfo!)
}else {
imageView.image = UIImage(named: myItemModel.selectedImageInfo!)
}
}
setNeedsLayout()
}
open override func preferredTitleZoomAnimateClosure(itemModel: JXSegmentedTitleItemModel, baseScale: CGFloat) -> JXSegmentedCellSelectedAnimationClosure {
guard let myItemModel = itemModel as? JXSegmentedTitleOrImageItemModel else {
return super.preferredTitleZoomAnimateClosure(itemModel: itemModel, baseScale: baseScale)
}
if myItemModel.selectedImageInfo == nil && myItemModel.isSelected {
//item
return super.preferredTitleZoomAnimateClosure(itemModel: itemModel, baseScale: baseScale)
}else {
let closure: JXSegmentedCellSelectedAnimationClosure = {[weak self] (percent) in
if itemModel.isSelected {
//
itemModel.titleCurrentZoomScale = itemModel.titleSelectedZoomScale
}else {
//
itemModel.titleCurrentZoomScale = itemModel.titleNormalZoomScale
}
let currentTransform = CGAffineTransform(scaleX: baseScale*itemModel.titleCurrentZoomScale, y: baseScale*itemModel.titleCurrentZoomScale)
self?.titleLabel.transform = currentTransform
self?.maskTitleLabel.transform = currentTransform
}
//closure
closure(0)
return closure
}
}
open override func preferredTitleStrokeWidthAnimateClosure(itemModel: JXSegmentedTitleItemModel, attriText: NSMutableAttributedString) -> JXSegmentedCellSelectedAnimationClosure {
guard let myItemModel = itemModel as? JXSegmentedTitleOrImageItemModel else {
return super.preferredTitleStrokeWidthAnimateClosure(itemModel: itemModel, attriText: attriText)
}
if myItemModel.selectedImageInfo == nil && myItemModel.isSelected {
//item
return super.preferredTitleStrokeWidthAnimateClosure(itemModel: itemModel, attriText: attriText)
}else {
let closure: JXSegmentedCellSelectedAnimationClosure = {[weak self] (percent) in
if itemModel.isSelected {
//
itemModel.titleCurrentStrokeWidth = itemModel.titleSelectedStrokeWidth
}else {
//
itemModel.titleCurrentStrokeWidth = itemModel.titleNormalStrokeWidth
}
attriText.addAttributes([NSAttributedString.Key.strokeWidth: itemModel.titleCurrentStrokeWidth], range: NSRange(location: 0, length: attriText.string.count))
self?.titleLabel.attributedText = attriText
}
//closure
closure(0)
return closure
}
}
open override func preferredTitleColorAnimateClosure(itemModel: JXSegmentedTitleItemModel) -> JXSegmentedCellSelectedAnimationClosure {
guard let myItemModel = itemModel as? JXSegmentedTitleOrImageItemModel else {
return super.preferredTitleColorAnimateClosure(itemModel: itemModel)
}
if myItemModel.selectedImageInfo == nil && myItemModel.isSelected {
//item
return super.preferredTitleColorAnimateClosure(itemModel: itemModel)
}else {
let closure: JXSegmentedCellSelectedAnimationClosure = {[weak self] (percent) in
if itemModel.isSelected {
//
itemModel.titleCurrentColor = itemModel.titleSelectedColor
}else {
//
itemModel.titleCurrentColor = itemModel.titleNormalColor
}
self?.titleLabel.textColor = itemModel.titleCurrentColor
}
//closure
closure(0)
return closure
}
}
}

View File

@ -0,0 +1,51 @@
//
// JXSegmentedTitleOrImageDataSource.swift
// JXSegmentedView
//
// Created by jiaxin on 2019/1/22.
// Copyright © 2019 jiaxin. All rights reserved.
//
import UIKit
open class JXSegmentedTitleOrImageDataSource: JXSegmentedTitleDataSource {
/// itemImageNamenil
open var selectedImageInfos: [String?]?
/// UIImage(named:)
open var loadImageClosure: LoadImageClosure?
///
open var imageSize: CGSize = CGSize(width: 30, height: 30)
open override func preferredItemModelInstance() -> JXSegmentedBaseItemModel {
return JXSegmentedTitleOrImageItemModel()
}
open override func reloadData(selectedIndex: Int) {
selectedAnimationDuration = 0.1
super.reloadData(selectedIndex: selectedIndex)
}
open override func preferredRefreshItemModel( _ itemModel: JXSegmentedBaseItemModel, at index: Int, selectedIndex: Int) {
super.preferredRefreshItemModel(itemModel, at: index, selectedIndex: selectedIndex)
guard let itemModel = itemModel as? JXSegmentedTitleOrImageItemModel else {
return
}
itemModel.selectedImageInfo = selectedImageInfos?[index]
itemModel.loadImageClosure = loadImageClosure
itemModel.imageSize = imageSize
}
//MARK: - JXSegmentedViewDataSource
open override func registerCellClass(in segmentedView: JXSegmentedView) {
segmentedView.collectionView.register(JXSegmentedTitleOrImageCell.self, forCellWithReuseIdentifier: "cell")
}
open override func segmentedView(_ segmentedView: JXSegmentedView, cellForItemAt index: Int) -> JXSegmentedBaseCell {
let cell = segmentedView.dequeueReusableCell(withReuseIdentifier: "cell", at: index)
return cell
}
}

View File

@ -0,0 +1,15 @@
//
// JXSegmentedTitleOrImageItemModel.swift
// JXSegmentedView
//
// Created by jiaxin on 2019/1/22.
// Copyright © 2019 jiaxin. All rights reserved.
//
import UIKit
open class JXSegmentedTitleOrImageItemModel: JXSegmentedTitleItemModel {
open var selectedImageInfo: String?
open var loadImageClosure: LoadImageClosure?
open var imageSize: CGSize = CGSize.zero
}

10
Pods/Manifest.lock generated
View File

@ -1,6 +1,8 @@
PODS:
- Alamofire (5.9.1)
- IQKeyboardManagerSwift (6.5.13)
- JXPagingView/Paging (2.1.3)
- JXSegmentedView (1.3.3)
- Kingfisher (7.11.0)
- SnapKit (5.6.0)
- SVProgressHUD (2.2.5)
@ -9,6 +11,8 @@ PODS:
DEPENDENCIES:
- Alamofire
- IQKeyboardManagerSwift
- JXPagingView/Paging
- JXSegmentedView
- Kingfisher
- SnapKit
- SVProgressHUD
@ -18,6 +22,8 @@ SPEC REPOS:
trunk:
- Alamofire
- IQKeyboardManagerSwift
- JXPagingView
- JXSegmentedView
- Kingfisher
- SnapKit
- SVProgressHUD
@ -26,11 +32,13 @@ SPEC REPOS:
SPEC CHECKSUMS:
Alamofire: f36a35757af4587d8e4f4bfa223ad10be2422b8c
IQKeyboardManagerSwift: 69b5fb9960edff37263d45c44fd97427e1f8c22b
JXPagingView: afdd2e9af09c90160dd232b970d603cc6e7ddd0e
JXSegmentedView: 651b60fcf705258ba9395edd53876dbd2853fb68
Kingfisher: b9c985d864d43515f404f1ef4a8ce7d802ace3ac
SnapKit: e01d52ebb8ddbc333eefe2132acf85c8227d9c25
SVProgressHUD: 1428aafac632c1f86f62aa4243ec12008d7a51d6
SwiftDate: 72d28954e8e1c6c1c0f917ccc8005e4f83c7d4b2
PODFILE CHECKSUM: d3eda313abd465652ca5c62aaab483d8f02ed2d2
PODFILE CHECKSUM: c0bbca53cdc7c53ea7a9dbc8d75b7ae964a1dbc2
COCOAPODS: 1.15.2

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>${PODS_DEVELOPMENT_LANGUAGE}</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.1.3</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

View File

@ -0,0 +1,5 @@
#import <Foundation/Foundation.h>
@interface PodsDummy_JXPagingView : NSObject
@end
@implementation PodsDummy_JXPagingView
@end

View File

@ -0,0 +1,12 @@
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif

View File

@ -0,0 +1,16 @@
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
FOUNDATION_EXPORT double JXPagingViewVersionNumber;
FOUNDATION_EXPORT const unsigned char JXPagingViewVersionString[];

View File

@ -0,0 +1,14 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/JXPagingView
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/JXPagingView
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES

View File

@ -0,0 +1,6 @@
framework module JXPagingView {
umbrella header "JXPagingView-umbrella.h"
export *
module * { export * }
}

View File

@ -0,0 +1,14 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/JXPagingView
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/JXPagingView
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>${PODS_DEVELOPMENT_LANGUAGE}</string>
<key>CFBundleIdentifier</key>
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>2.1.3</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>${PODS_DEVELOPMENT_LANGUAGE}</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.3.3</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

View File

@ -0,0 +1,5 @@
#import <Foundation/Foundation.h>
@interface PodsDummy_JXSegmentedView : NSObject
@end
@implementation PodsDummy_JXSegmentedView
@end

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