Commit 3b6941d8 authored by 乔金柱's avatar 乔金柱

Merge branch 'lym/text' into 'master'

Lym/text

See merge request !21
parents 4a4ba4ae 9e91ae10
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
04393781246AA49D00EAE014 /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04393780246AA49D00EAE014 /* File.swift */; };
0AB11836BD16E7A177F2CBDF /* Pods_GMBase_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A6E1B1FCE1B7696F7DE8B8E9 /* Pods_GMBase_Example.framework */; }; 0AB11836BD16E7A177F2CBDF /* Pods_GMBase_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A6E1B1FCE1B7696F7DE8B8E9 /* Pods_GMBase_Example.framework */; };
29C10D9EC61CDA2F6F466BD4 /* Pods_GMBase_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7E15FCB251AFC31B335265AF /* Pods_GMBase_Tests.framework */; }; 29C10D9EC61CDA2F6F466BD4 /* Pods_GMBase_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7E15FCB251AFC31B335265AF /* Pods_GMBase_Tests.framework */; };
6003F58E195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; }; 6003F58E195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; };
...@@ -40,6 +41,7 @@ ...@@ -40,6 +41,7 @@
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
04393780246AA49D00EAE014 /* File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = "<group>"; };
6003F58A195388D20070C39A /* GMBase_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GMBase_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 6003F58A195388D20070C39A /* GMBase_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GMBase_Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
6003F58D195388D20070C39A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 6003F58D195388D20070C39A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
6003F58F195388D20070C39A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 6003F58F195388D20070C39A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
...@@ -150,6 +152,7 @@ ...@@ -150,6 +152,7 @@
D32ECC461D33946600AEF0D0 /* GMListViewController.h */, D32ECC461D33946600AEF0D0 /* GMListViewController.h */,
D32ECC471D33946600AEF0D0 /* GMListViewController.m */, D32ECC471D33946600AEF0D0 /* GMListViewController.m */,
D3EB0CC21D33968300EEDD8B /* GMListViewModel.h */, D3EB0CC21D33968300EEDD8B /* GMListViewModel.h */,
04393780246AA49D00EAE014 /* File.swift */,
D3EB0CC31D33968300EEDD8B /* GMListViewModel.m */, D3EB0CC31D33968300EEDD8B /* GMListViewModel.m */,
D32ECC411D33840600AEF0D0 /* GMDetailController.h */, D32ECC411D33840600AEF0D0 /* GMDetailController.h */,
D32ECC421D33840600AEF0D0 /* GMDetailController.m */, D32ECC421D33840600AEF0D0 /* GMDetailController.m */,
...@@ -225,6 +228,7 @@ ...@@ -225,6 +228,7 @@
6003F587195388D20070C39A /* Frameworks */, 6003F587195388D20070C39A /* Frameworks */,
6003F588195388D20070C39A /* Resources */, 6003F588195388D20070C39A /* Resources */,
20C43A9AD7543F1CB63A4FB9 /* [CP] Embed Pods Frameworks */, 20C43A9AD7543F1CB63A4FB9 /* [CP] Embed Pods Frameworks */,
9EF5C8615510ED2B25E84552 /* [CP] Copy Pods Resources */,
); );
buildRules = ( buildRules = (
); );
...@@ -275,6 +279,7 @@ ...@@ -275,6 +279,7 @@
developmentRegion = English; developmentRegion = English;
hasScannedForEncodings = 0; hasScannedForEncodings = 0;
knownRegions = ( knownRegions = (
English,
en, en,
Base, Base,
); );
...@@ -320,34 +325,50 @@ ...@@ -320,34 +325,50 @@
inputPaths = ( inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-GMBase_Example/Pods-GMBase_Example-frameworks.sh", "${PODS_ROOT}/Target Support Files/Pods-GMBase_Example/Pods-GMBase_Example-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework", "${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework",
"${BUILT_PRODUCTS_DIR}/GMBase/GMBase.framework", "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework",
"${BUILT_PRODUCTS_DIR}/EVReflection/EVReflection.framework",
"${BUILT_PRODUCTS_DIR}/GM-Swift-Observable/GM_Swift_Observable.framework",
"${BUILT_PRODUCTS_DIR}/GMCache/GMCache.framework", "${BUILT_PRODUCTS_DIR}/GMCache/GMCache.framework",
"${BUILT_PRODUCTS_DIR}/GMFoundation/GMFoundation.framework",
"${BUILT_PRODUCTS_DIR}/GMHud/GMHud.framework", "${BUILT_PRODUCTS_DIR}/GMHud/GMHud.framework",
"${BUILT_PRODUCTS_DIR}/GMJSONModel/GMJSONModel.framework", "${BUILT_PRODUCTS_DIR}/GMJSONModel/GMJSONModel.framework",
"${BUILT_PRODUCTS_DIR}/GMKit/GMKit.framework", "${BUILT_PRODUCTS_DIR}/GMKit/GMKit.framework",
"${BUILT_PRODUCTS_DIR}/GMNetService/GMNetService.framework", "${BUILT_PRODUCTS_DIR}/GMNetService/GMNetService.framework",
"${BUILT_PRODUCTS_DIR}/GMNetworking/GMNetworking.framework",
"${BUILT_PRODUCTS_DIR}/GMPhobos/GMPhobos.framework", "${BUILT_PRODUCTS_DIR}/GMPhobos/GMPhobos.framework",
"${BUILT_PRODUCTS_DIR}/GMRefresh/GMRefresh.framework", "${BUILT_PRODUCTS_DIR}/GMRefresh/GMRefresh.framework",
"${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework", "${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework",
"${BUILT_PRODUCTS_DIR}/MJExtension/MJExtension.framework",
"${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework",
"${BUILT_PRODUCTS_DIR}/MagicalRecord/MagicalRecord.framework",
"${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework", "${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework",
"${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework", "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework",
"${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework",
"${BUILT_PRODUCTS_DIR}/TMCache/TMCache.framework", "${BUILT_PRODUCTS_DIR}/TMCache/TMCache.framework",
"${BUILT_PRODUCTS_DIR}/UITableView+FDTemplateLayoutCell/UITableView_FDTemplateLayoutCell.framework", "${BUILT_PRODUCTS_DIR}/UITableView+FDTemplateLayoutCell/UITableView_FDTemplateLayoutCell.framework",
); );
name = "[CP] Embed Pods Frameworks"; name = "[CP] Embed Pods Frameworks";
outputPaths = ( outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AFNetworking.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AFNetworking.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMBase.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/EVReflection.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GM_Swift_Observable.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMCache.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMCache.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMFoundation.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMHud.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMHud.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMJSONModel.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMJSONModel.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMKit.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMNetService.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMNetService.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMNetworking.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMPhobos.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMPhobos.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMRefresh.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMRefresh.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MBProgressHUD.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MBProgressHUD.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MJExtension.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MJRefresh.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MagicalRecord.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Masonry.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Masonry.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TMCache.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TMCache.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/UITableView_FDTemplateLayoutCell.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/UITableView_FDTemplateLayoutCell.framework",
); );
...@@ -374,6 +395,24 @@ ...@@ -374,6 +395,24 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
9EF5C8615510ED2B25E84552 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-GMBase_Example/Pods-GMBase_Example-resources.sh",
"${PODS_ROOT}/Weibo_SDK/libWeiboSDK/WeiboSDK.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/WeiboSDK.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-GMBase_Example/Pods-GMBase_Example-resources.sh\"\n";
showEnvVarsInLog = 0;
};
E11DAE1A31A4A404D5D11E70 /* [CP] Check Pods Manifest.lock */ = { E11DAE1A31A4A404D5D11E70 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
...@@ -401,6 +440,7 @@ ...@@ -401,6 +440,7 @@
files = ( files = (
D32ECC481D33946600AEF0D0 /* GMListViewController.m in Sources */, D32ECC481D33946600AEF0D0 /* GMListViewController.m in Sources */,
6003F59E195388D20070C39A /* GMAppDelegate.m in Sources */, 6003F59E195388D20070C39A /* GMAppDelegate.m in Sources */,
04393781246AA49D00EAE014 /* File.swift in Sources */,
6003F5A7195388D20070C39A /* GMViewController.m in Sources */, 6003F5A7195388D20070C39A /* GMViewController.m in Sources */,
D32ECC431D33840600AEF0D0 /* GMDetailController.m in Sources */, D32ECC431D33840600AEF0D0 /* GMDetailController.m in Sources */,
D3EB0CC41D33968300EEDD8B /* GMListViewModel.m in Sources */, D3EB0CC41D33968300EEDD8B /* GMListViewModel.m in Sources */,
...@@ -535,7 +575,9 @@ ...@@ -535,7 +575,9 @@
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "GMBase/GMBase_Example-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "GMBase/GMBase_Example-Bridging-Header.h";
SWIFT_OBJC_INTERFACE_HEADER_NAME = "$GMBase-Swift.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.2;
WRAPPER_EXTENSION = app; WRAPPER_EXTENSION = app;
}; };
name = Debug; name = Debug;
...@@ -556,6 +598,8 @@ ...@@ -556,6 +598,8 @@
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "GMBase/GMBase_Example-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "GMBase/GMBase_Example-Bridging-Header.h";
SWIFT_OBJC_INTERFACE_HEADER_NAME = "$GMBase-Swift.h";
SWIFT_VERSION = 4.2;
WRAPPER_EXTENSION = app; WRAPPER_EXTENSION = app;
}; };
name = Release; name = Release;
...@@ -564,6 +608,7 @@ ...@@ -564,6 +608,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = E10ABE29C983D16788B32755 /* Pods-GMBase_Tests.debug.xcconfig */; baseConfigurationReference = E10ABE29C983D16788B32755 /* Pods-GMBase_Tests.debug.xcconfig */;
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(SDKROOT)/Developer/Library/Frameworks", "$(SDKROOT)/Developer/Library/Frameworks",
...@@ -588,6 +633,7 @@ ...@@ -588,6 +633,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 78552CB3DCC72D38AB9DD053 /* Pods-GMBase_Tests.release.xcconfig */; baseConfigurationReference = 78552CB3DCC72D38AB9DD053 /* Pods-GMBase_Tests.release.xcconfig */;
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(SDKROOT)/Developer/Library/Frameworks", "$(SDKROOT)/Developer/Library/Frameworks",
......
//
// File.swift
// GMBase_Example
//
// Created by Q14 on 2020/5/12.
// Copyright © 2020 wangyang. All rights reserved.
//
import Foundation
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
// Copyright © 2016年 wangyang. All rights reserved. // Copyright © 2016年 wangyang. All rights reserved.
// //
@import GMBase; //@import GMBase;
#import "GMListViewModel.h" #import "GMListViewModel.h"
@interface GMListViewController : WMBaseListViewController @interface GMListViewController : WMBaseListViewController
......
use_frameworks! use_frameworks!
inhibit_all_warnings! inhibit_all_warnings!
platform :ios, '8.0' platform :ios, '9.0'
#open source #open source
source 'https://github.com/CocoaPods/Specs.git' source 'https://github.com/CocoaPods/Specs.git'
#our company #our company
...@@ -8,7 +8,11 @@ source 'git@git.wanmeizhensuo.com:gengmeiios/GMSpecs.git' ...@@ -8,7 +8,11 @@ source 'git@git.wanmeizhensuo.com:gengmeiios/GMSpecs.git'
target 'GMBase_Example' do target 'GMBase_Example' do
pod 'GMBase', :path => '../' pod 'GMBase', :path => '../'
pod 'SnapKit', '4.2.0'
pod 'GMShareSDK', '0.2.1'
pod 'GMBaseSwift', :path => '../../GMBaseSwift'
pod 'GMPhobos', :git => 'git@git.wanmeizhensuo.com:gengmeiios/GMPhobos.git', :commit => '7e58199898627699373ad6a9f87b875ab04b0927'
target 'GMBase_Tests' do target 'GMBase_Tests' do
inherit! :search_paths inherit! :search_paths
......
...@@ -14,110 +14,202 @@ PODS: ...@@ -14,110 +14,202 @@ PODS:
- AFNetworking/Serialization (3.1.0) - AFNetworking/Serialization (3.1.0)
- AFNetworking/UIKit (3.1.0): - AFNetworking/UIKit (3.1.0):
- AFNetworking/NSURLSession - AFNetworking/NSURLSession
- Alamofire (4.7.0)
- BDOpenSDKKit (1.0.0)
- DouyinOpenSDK (1.4.1):
- BDOpenSDKKit (~> 1.0.0)
- EVReflection (5.10.0):
- EVReflection/Core (= 5.10.0)
- EVReflection/Core (5.10.0)
- GM-Swift-Observable (4.0.1)
- GMBase (1.1.5): - GMBase (1.1.5):
- GMBaseSwift
- GMHud - GMHud
- GMJSONModel - GMJSONModel
- GMKit - GMKit
- GMNetService - GMNetService
- GMNetworking
- GMPhobos - GMPhobos
- GMRefresh - GMRefresh
- GMShareSDK
- MBProgressHUD - MBProgressHUD
- SDWebImage - SDWebImage
- "UITableView+FDTemplateLayoutCell (= 1.4)" - "UITableView+FDTemplateLayoutCell (= 1.4)"
- GMBaseSwift (3.4.1):
- EVReflection (= 5.10.0)
- GM-Swift-Observable
- GMHud
- GMKit
- GMNetworking
- GMPhobos
- GMRefresh
- SnapKit (= 4.2.0)
- GMCache (1.0.1): - GMCache (1.0.1):
- TMCache (= 2.1.0) - TMCache (= 2.1.0)
- GMHud (1.0.2): - GMFoundation (1.0.8)
- GMHud (1.0.3):
- MBProgressHUD (= 0.9.2) - MBProgressHUD (= 0.9.2)
- GMJSONModel (1.7.4) - GMJSONModel (1.7.4)
- GMKit (1.1.4): - GMKit (1.2.6):
- GMKit/Category (= 1.1.4) - GMKit/Category (= 1.2.6)
- GMKit/Color (= 1.1.4) - GMKit/Color (= 1.2.6)
- GMKit/Constant (= 1.1.4) - GMKit/Constant (= 1.2.6)
- GMKit/FDFullscreenPopGesture (= 1.1.4) - GMKit/FDFullscreenPopGesture (= 1.2.6)
- GMKit/Kit (= 1.1.4) - GMKit/Kit (= 1.2.6)
- GMKit/Protocol (= 1.1.4) - GMKit/Protocol (= 1.2.6)
- Masonry - Masonry
- SDWebImage - SDWebImage
- GMKit/Category (1.1.4): - SnapKit
- GMKit/Color (= 1.1.4) - GMKit/Category (1.2.6):
- GMKit/Constant (= 1.1.4) - GMKit/Color (= 1.2.6)
- GMKit/Protocol (= 1.1.4) - GMKit/Constant (= 1.2.6)
- GMKit/Protocol (= 1.2.6)
- Masonry - Masonry
- SDWebImage - SDWebImage
- GMKit/Color (1.1.4): - SnapKit
- GMKit/Color (1.2.6):
- Masonry - Masonry
- SDWebImage - SDWebImage
- GMKit/Constant (1.1.4): - SnapKit
- GMKit/Constant (1.2.6):
- Masonry - Masonry
- SDWebImage - SDWebImage
- GMKit/FDFullscreenPopGesture (1.1.4): - SnapKit
- GMKit/FDFullscreenPopGesture (1.2.6):
- Masonry - Masonry
- SDWebImage - SDWebImage
- GMKit/Kit (1.1.4): - SnapKit
- GMKit/Category (= 1.1.4) - GMKit/Kit (1.2.6):
- GMKit/Color (= 1.1.4) - GMKit/Category (= 1.2.6)
- GMKit/Constant (= 1.1.4) - GMKit/Color (= 1.2.6)
- GMKit/Protocol (= 1.1.4) - GMKit/Constant (= 1.2.6)
- GMKit/Protocol (= 1.2.6)
- Masonry - Masonry
- SDWebImage - SDWebImage
- GMKit/Protocol (1.1.4): - SnapKit
- GMKit/Protocol (1.2.6):
- Masonry - Masonry
- SDWebImage - SDWebImage
- SnapKit
- GMNetService (1.0.2): - GMNetService (1.0.2):
- AFNetworking (= 3.1.0) - AFNetworking (= 3.1.0)
- GMPhobos (1.3.5): - GMNetworking (4.0.7):
- Alamofire (= 4.7.0)
- GMPhobos (2.0.4):
- GMCache - GMCache
- MagicalRecord
- MJExtension
- GMRefresh (1.0.4):
- GMPhobos
- MJRefresh
- GMShareSDK (0.2.1):
- DouyinOpenSDK (= 1.4.1)
- GMFoundation
- GMJSONModel
- GMKit - GMKit
- GMRefresh (1.0.2):
- GMPhobos - GMPhobos
- Masonry
- MBProgressHUD (= 0.9.2)
- WechatOpenSDK (= 1.8.6.2)
- Weibo_SDK (= 3.2.7)
- MagicalRecord (2.3.2):
- MagicalRecord/Core (= 2.3.2)
- MagicalRecord/Core (2.3.2)
- Masonry (1.1.0) - Masonry (1.1.0)
- MBProgressHUD (0.9.2) - MBProgressHUD (0.9.2)
- SDWebImage (5.1.1): - MJExtension (3.2.1)
- SDWebImage/Core (= 5.1.1) - MJRefresh (3.4.3)
- SDWebImage/Core (5.1.1) - SDWebImage (5.8.0):
- SDWebImage/Core (= 5.8.0)
- SDWebImage/Core (5.8.0)
- SnapKit (4.2.0)
- TMCache (2.1.0) - TMCache (2.1.0)
- "UITableView+FDTemplateLayoutCell (1.4)" - "UITableView+FDTemplateLayoutCell (1.4)"
- WechatOpenSDK (1.8.6.2)
- Weibo_SDK (3.2.7)
DEPENDENCIES: DEPENDENCIES:
- GMBase (from `../`) - GMBase (from `../`)
- GMBaseSwift (from `../../GMBaseSwift`)
- "GMPhobos (from `git@git.wanmeizhensuo.com:gengmeiios/GMPhobos.git`, commit `7e58199898627699373ad6a9f87b875ab04b0927`)"
- GMShareSDK (= 0.2.1)
- SnapKit (= 4.2.0)
SPEC REPOS: SPEC REPOS:
"git@git.wanmeizhensuo.com:gengmeiios/GMSpecs.git": "git@git.wanmeizhensuo.com:gengmeiios/GMSpecs.git":
- GM-Swift-Observable
- GMCache - GMCache
- GMFoundation
- GMHud - GMHud
- GMJSONModel - GMJSONModel
- GMKit - GMKit
- GMNetService - GMNetService
- GMPhobos - GMNetworking
- GMRefresh - GMRefresh
- GMShareSDK
https://github.com/CocoaPods/Specs.git: https://github.com/CocoaPods/Specs.git:
- AFNetworking - AFNetworking
- Alamofire
- BDOpenSDKKit
- DouyinOpenSDK
- EVReflection
- MagicalRecord
- Masonry - Masonry
- MBProgressHUD - MBProgressHUD
- MJExtension
- MJRefresh
- SDWebImage - SDWebImage
- SnapKit
- TMCache - TMCache
- "UITableView+FDTemplateLayoutCell" - "UITableView+FDTemplateLayoutCell"
- WechatOpenSDK
- Weibo_SDK
EXTERNAL SOURCES: EXTERNAL SOURCES:
GMBase: GMBase:
:path: "../" :path: "../"
GMBaseSwift:
:path: "../../GMBaseSwift"
GMPhobos:
:commit: 7e58199898627699373ad6a9f87b875ab04b0927
:git: "git@git.wanmeizhensuo.com:gengmeiios/GMPhobos.git"
CHECKOUT OPTIONS:
GMPhobos:
:commit: 7e58199898627699373ad6a9f87b875ab04b0927
:git: "git@git.wanmeizhensuo.com:gengmeiios/GMPhobos.git"
SPEC CHECKSUMS: SPEC CHECKSUMS:
AFNetworking: 5e0e199f73d8626b11e79750991f5d173d1f8b67 AFNetworking: 5e0e199f73d8626b11e79750991f5d173d1f8b67
GMBase: b747cd3ecd7a5109469113cf48ce04ae6d16cdbe Alamofire: 907e0a98eb68cdb7f9d1f541a563d6ac5dc77b25
BDOpenSDKKit: 3fb530ce73f85a7d6ee69e7fd3d9158444c5bd09
DouyinOpenSDK: 5ba83de22963ba7a3ba70c8ff11dfcb2885ecc2b
EVReflection: 1abc1a81927ab0d30170238cf9b79bff489e9728
GM-Swift-Observable: 756d8fc13638b9faa68cb10266b2ffb47a911595
GMBase: 358e72062cb0f64a57ff34ec181b20ad9973d79b
GMBaseSwift: 8f327883b33ac24d0dcdb648ac3f9d6c57949ad5
GMCache: b78d8e46db864405e91d226ce640cc80d966c611 GMCache: b78d8e46db864405e91d226ce640cc80d966c611
GMHud: 45aa755b72a65f89d810430336803086359c0fb1 GMFoundation: e9f7fd9c6e5f133e09009b0ee5de4fce60ac5682
GMHud: 18d41f4900a204f27be14e9504fcee2060ae3b2c
GMJSONModel: 5e81a98de668e9f93cf6ff77869f77b0d1a806be GMJSONModel: 5e81a98de668e9f93cf6ff77869f77b0d1a806be
GMKit: 11c9ab9a317f381a05b0e1e577dd95a495625edf GMKit: ff2a77a9b85e13b4de333ec5eeb10b1ffef09756
GMNetService: 7c69e7981b4fac29657fad5fc86a0c453196b862 GMNetService: 7c69e7981b4fac29657fad5fc86a0c453196b862
GMPhobos: 1e2d68c456b69bf156276d7242877498107474db GMNetworking: 592b9b71f2a7d92203483276158ce3139ac789d2
GMRefresh: 9d2c521e3d49b68d3c736fb138a87fae4adb05f9 GMPhobos: c09920b4a726363b902c2f14ba64e1ee1620d0e2
GMRefresh: c01ff8de5ada92e1362602fb6991f99124b7dbe3
GMShareSDK: a0725e2848d043aec84e787f6d233ac1546c1661
MagicalRecord: 53bed74b4323b930992a725be713e53b37d19755
Masonry: 678fab65091a9290e40e2832a55e7ab731aad201 Masonry: 678fab65091a9290e40e2832a55e7ab731aad201
MBProgressHUD: 1569cf7ace17a8bac47aabfbb8580a49690386d1 MBProgressHUD: 1569cf7ace17a8bac47aabfbb8580a49690386d1
SDWebImage: 96d7f03415ccb28d299d765f93557ff8a617abd8 MJExtension: 635f2c663dcb1bf76fa4b715b2570a5710aec545
MJRefresh: 53e3e3219f204425ee6d3e62e8733d3295944cd6
SDWebImage: 84000f962cbfa70c07f19d2234cbfcf5d779b5dc
SnapKit: fe8a619752f3f27075cc9a90244d75c6c3f27e2a
TMCache: 95ebcc9b3c7e90fb5fd8fc3036cba3aa781c9bed TMCache: 95ebcc9b3c7e90fb5fd8fc3036cba3aa781c9bed
"UITableView+FDTemplateLayoutCell": 234e1582bcc4e18461af91155123bb96538ed030 "UITableView+FDTemplateLayoutCell": 234e1582bcc4e18461af91155123bb96538ed030
WechatOpenSDK: 8b3ba4239193d1112205c139c94e21934e8f369a
Weibo_SDK: 5a4d08f7e1fedbb635435e4585c8c0439c7da089
PODFILE CHECKSUM: b9c24bb8fb1ea50b58c59e079b456b4ca1d9b998 PODFILE CHECKSUM: db60bbf458f56874386142fa246e256c5fa92944
COCOAPODS: 1.9.1 COCOAPODS: 1.9.1
...@@ -8,7 +8,7 @@ Pod::Spec.new do |s| ...@@ -8,7 +8,7 @@ Pod::Spec.new do |s|
s.source = { :git => 'git@git.wanmeizhensuo.com:gengmeiios/GMBase.git', :tag => s.version.to_s } s.source = { :git => 'git@git.wanmeizhensuo.com:gengmeiios/GMBase.git', :tag => s.version.to_s }
s.ios.deployment_target = '8.0' s.ios.deployment_target = '8.0'
s.source_files = 'GMBase/Classes/**/*' s.source_files = 'GMBase/Classes/**/*'
s.dependency 'GMRefresh' s.dependency 'GMRefresh'
s.dependency 'GMJSONModel' s.dependency 'GMJSONModel'
s.dependency 'UITableView+FDTemplateLayoutCell', '1.4' s.dependency 'UITableView+FDTemplateLayoutCell', '1.4'
...@@ -18,4 +18,15 @@ Pod::Spec.new do |s| ...@@ -18,4 +18,15 @@ Pod::Spec.new do |s|
s.dependency 'GMHud' s.dependency 'GMHud'
s.dependency 'GMKit' s.dependency 'GMKit'
s.dependency 'SDWebImage' s.dependency 'SDWebImage'
# s.dependency 'SnapKit', '4.0.0'
s.dependency 'GMShareSDK'
s.dependency 'GMNetworking'
# s.dependency 'PLMediaStreamingKit'
# s.dependency 'PLPlayerKit'
# s.dependency 'GMAlbum'
# SnapKit 4.2.0
# s.dependency 'SnapKit', '4.2.0'
s.dependency 'GMBaseSwift'
s.swift_version = '4.2'
s.static_framework = true
end end
//
// GMAddressManager.h
// Gengmei
//
// Created by 翟国钧 on 16/9/28.
// Copyright © 2016年 更美互动信息科技有限公司. All rights reserved.
//
#import <GMBase/GMObject.h>
//@import GMBase;
typedef void (^AddressBookGetOnePersonBlock) (NSDictionary *result);
@interface GMAddressManager : GMObject
@property(nonatomic, copy) AddressBookGetOnePersonBlock getOneContactPersonBlock;
/**
获取通讯录联系人的授权方法
@author zhaiguojun 16-09-28 in 6.4.1
@return return value description
*/
- (void)getAddressBookAuthorization;
/**
获取一个用户选择的通讯录联系人
@author zhaiguojun 16-09-28 in 6.4.1
@return return value description
*/
- (void)chooseContactPersonAddressBookJsonString;
@end
//
// GMAddressManager.m
// Gengmei
//
// Created by 翟国钧 on 16/9/28.
// Copyright © 2016年 更美互动信息科技有限公司. All rights reserved.
//
#import <Contacts/Contacts.h>
#import <ContactsUI/ContactsUI.h>
#import <GMHud/GMHudModule.h>
#import <AddressBook/AddressBook.h>
#import <AddressBookUI/ABPeoplePickerNavigationController.h>
#import <AddressBook/ABPerson.h>
#import <AddressBookUI/ABPersonViewController.h>
#import "GMBaseTool.h"
#import <GMKit/GMSafeValue.h>
#import "GMAddressManager.h"
#import <GMFoundation/GMFoundation-umbrella.h>
//#import <GMNetworking/GM>
@import GMNetworking;
@interface GMAddressManager ()<ABPeoplePickerNavigationControllerDelegate, CNContactPickerDelegate>
@end
@implementation GMAddressManager
- (instancetype)init
{
self = [super init];
if (self) {
}
return self;
}
- (void)getAddressBookAuthorization {
__weak __typeof(self)weakSelf = self;
if ([[[UIDevice currentDevice] systemVersion] compare:@"9.0" options:NSNumericSearch] == NSOrderedAscending)
{
[self checkAddressBookAuthorization:^(bool isAuthorized) {
if (isAuthorized) {
[weakSelf getAllAddress];
}
}];
} else {
[self checkAddressBookIos9:^(bool isAuthorized) {
if (isAuthorized) {
[weakSelf getAllAddress];
}
}];
}
}
- (void)getAllAddress {
NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:0];
if (([[[UIDevice currentDevice] systemVersion] compare:@"9.0" options:NSNumericSearch] == NSOrderedAscending)
) {
ABAuthorizationStatus status = ABAddressBookGetAuthorizationStatus();
// 2.如果是已经授权,才能获取联系人
if (status != kABAuthorizationStatusAuthorized) {
[GMHudModule toast:@"获取通讯录失败,请在设置中打开通讯录权限。"];
return ;
}
// 3.创建通信录对象
ABAddressBookRef addressBook = ABAddressBookCreate();
// 4.获取所有的联系人
CFArrayRef peopleArray = ABAddressBookCopyArrayOfAllPeople(addressBook);
CFIndex peopleCount = CFArrayGetCount(peopleArray);
// 5.遍历所有的联系人
for (int i = 0; i < peopleCount; i++) {
// 5.1.获取某一个联系人
ABRecordRef person = CFArrayGetValueAtIndex(peopleArray, i);
// 5.2.获取联系人的姓名
NSString *lastName = (__bridge_transfer NSString *)ABRecordCopyValue(person, kABPersonLastNameProperty) ?:@"";
NSString *firstName = (__bridge_transfer NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty) ?:@"";
NSString *fullName = [NSString stringWithFormat:@"%@%@", lastName, firstName];
ABMultiValueRef phones = ABRecordCopyValue(person, kABPersonPhoneProperty);
NSString *phoneValue = (__bridge_transfer NSString *)ABMultiValueCopyValueAtIndex(phones, 0) ?:@"";
[array addObject:@{@"name":fullName,@"phone":phoneValue}];
CFRelease(phones);
}
CFRelease(addressBook);
CFRelease(peopleArray);
[self sendData:[NSString convertToPrettyPrintedJsonString:array]];
} else {
CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
if (status != CNAuthorizationStatusAuthorized) {
[GMHudModule toast:@"获取通讯录失败,请在设置中打开通讯录权限。"];
return ;
}
CNContactStore *contactStore = [[CNContactStore alloc] init];
NSArray *keys = @[CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey];
CNContactFetchRequest *request = [[CNContactFetchRequest alloc] initWithKeysToFetch:keys];
[contactStore enumerateContactsWithFetchRequest:request error:nil usingBlock:^(CNContact * _Nonnull contact, BOOL * _Nonnull stop) {
NSString *lastName = contact.familyName ?:@"";
NSString *firstName = contact.givenName ?:@"";
NSString *fullName = [NSString stringWithFormat:@"%@%@",lastName , firstName];
CNLabeledValue *phones = contact.phoneNumbers.firstObject;
NSString *phoneValue = @"";
if (phones != nil) {
CNPhoneNumber *phoneNumer = phones.value;
phoneValue = phoneNumer.stringValue ?:@"";
}
[array addObject:@{@"name":fullName,@"phone":phoneValue}];
}];
[self sendData:[NSString convertToPrettyPrintedJsonString:array]];
}
}
- (void)sendData:(NSString *)addressStr {
// 仅仅发送,不给用户任何反馈
[GMNetworking requestOCWithApi:@"/api/addressbook/upload" method:GMHTTPMethodPost parameters:@{@"address":addressStr} completion:nil];
}
- (void)chooseContactPersonAddressBookJsonString {
if ([[[UIDevice currentDevice] systemVersion] compare:@"9.0" options:NSNumericSearch] == NSOrderedAscending) {
ABAuthorizationStatus status = ABAddressBookGetAuthorizationStatus();
if (status != kABAuthorizationStatusAuthorized) {
[GMHudModule toast:@"获取通讯录失败,请在设置中打开通讯录权限。"];
return ;
}
ABPeoplePickerNavigationController *nav = [[ABPeoplePickerNavigationController alloc] init];
nav.peoplePickerDelegate = self;
nav.predicateForSelectionOfPerson = [NSPredicate predicateWithValue:false];
[[GMBaseTool getNavigation] pushViewController:nav animated:YES];
} else {
[self checkAddressBookIos9:^(bool isAuthorized) {
if (!isAuthorized) {
[GMHudModule toast:@"获取通讯录失败,请在设置中打开通讯录权限。"];
return ;
}
}];
CNContactPickerViewController *nav = [[CNContactPickerViewController alloc] init];
nav.delegate = self;
[[GMBaseTool getNavigation] pushViewController:nav animated:YES];
}
}
#pragma -mark ABPeoplePickerNavigationControllerDelegate method
/**
获取一个用户选择的通讯录联系人成功的回调
@author zhaiguojun 16-09-28 in 6.4.1
@param peoplePicker peoplePicker description
@param person person description
@param property property description
@param identifier identifier description
*/
- (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier {
NSString *fullName = CFBridgingRelease(ABRecordCopyCompositeName(person)) ?:@"";
ABMultiValueRef phones = ABRecordCopyValue(person, kABPersonPhoneProperty);
NSString *phoneNum = CFBridgingRelease(ABMultiValueCopyValueAtIndex(phones, 0)) ?:@"";
phoneNum = [phoneNum stringByReplacingOccurrencesOfString:@"-" withString:@""];
//释放CF对象.如果没有纪录电话,phone是nil,不能释放。
if (phones != nil) {
CFRelease(phones);
}
if (self.getOneContactPersonBlock) {
self.getOneContactPersonBlock(@{@"name":fullName, @"phone":phoneNum});
}
}
/**
取消获取电话
@author zhaiguojun 16-09-28 in 6.4.0
@param peoplePicker
@since 6.4.1
*/
- (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker {
[peoplePicker dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark - CNContactPickerDelegate method
- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContact:(CNContact *)contact {
// 1.获取联系人的姓名
NSString *firstname = contact.givenName;
// 2.获取联系人的电话号码
NSArray *phoneNums = contact.phoneNumbers;
CNLabeledValue *labeledValue = phoneNums.firstObject;
// 2.2.获取电话号码
CNPhoneNumber *phoneNumer = labeledValue.value;
NSString *phoneValue = phoneNumer.stringValue;
phoneValue = [phoneValue stringByReplacingOccurrencesOfString:@"-" withString:@""];
if (self.getOneContactPersonBlock) {
self.getOneContactPersonBlock(@{@"name":SafeValue(firstname), @"phone": SafeValue(phoneValue)});
}
}
- (void)contactPickerDidCancel:(CNContactPickerViewController *)picker{
[picker dismissViewControllerAnimated:YES completion:nil];
}
- (void)checkAddressBookAuthorization:(void (^)(bool isAuthorized))block {
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, NULL);
ABAuthorizationStatus authStatus = ABAddressBookGetAuthorizationStatus();
if (authStatus != kABAuthorizationStatusAuthorized) {
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
NSLog(@"Error: %@", (__bridge NSError *)error);
} else if (!granted) {
block(NO);
} else {
block(YES);
}
});
});
} else {
block(YES);
}
CFRelease(addressBook);
}
- (void)checkAddressBookIos9:(void (^)(bool isAuthorized))block {
CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
if (status == CNAuthorizationStatusNotDetermined) {
[[[CNContactStore alloc]init] requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (error) {
NSLog(@"Error:something run error!");
} else if (!granted) {
block(NO);
} else {
block(YES);
}
}];
}else if (status == CNAuthorizationStatusAuthorized){
block(YES);
}else {
block(NO);
}
}
@end
//
// GMBaseTool.h
// GMBase
//
// Created by 卢悦明 on 2020/5/13.
//
#import <Foundation/Foundation.h>
#import <GMbase/GMNavigationController.h>
NS_ASSUME_NONNULL_BEGIN
@interface GMBaseTool : NSObject
/// 获取当前控制器
+ (UIViewController *)getCurrentViewController;
/// 获取window
+ (UIWindow *)mainWindow;
/// 获取导航控制器
+ (GMNavigationController *)getNavigation;
+ (void)userIsAllowPush:(void (^)(BOOL))isOpenPushBlock;
@end
NS_ASSUME_NONNULL_END
//
// GMBaseTool.m
// GMBase
//
// Created by 卢悦明 on 2020/5/13.
//
#import "GMBaseTool.h"
#import <UserNotifications/UserNotifications.h>
@implementation GMBaseTool
+ (UIViewController *)getRootViewController{
UIWindow* window = [[[UIApplication sharedApplication] delegate] window];
return window.rootViewController;
}
+ (UIViewController *)getCurrentViewController{
UIViewController* currentViewController = [self getRootViewController];
BOOL runLoopFind = YES;
while (runLoopFind) {
if (currentViewController.presentedViewController) {
currentViewController = currentViewController.presentedViewController;
} else if ([currentViewController isKindOfClass:[UINavigationController class]]) {
UINavigationController* navigationController = (UINavigationController* )currentViewController;
currentViewController = [navigationController.childViewControllers lastObject];
} else if ([currentViewController isKindOfClass:[UITabBarController class]]) {
UITabBarController* tabBarController = (UITabBarController* )currentViewController;
currentViewController = tabBarController.selectedViewController;
} else {
NSUInteger childViewControllerCount = currentViewController.childViewControllers.count;
if (childViewControllerCount > 0) {
currentViewController = currentViewController.childViewControllers.lastObject;
return currentViewController;
} else {
return currentViewController;
}
}
}
return currentViewController;
}
//获取当前window
+ (UIWindow *)mainWindow
{
UIApplication *app = [UIApplication sharedApplication];
if ([app.delegate respondsToSelector:@selector(window)]) {
return [app.delegate window];
} else {
return [app keyWindow];
}
}
+ (GMNavigationController *)getNavigation {
UIViewController* rootVC = [self getRootViewController];
if ([rootVC isKindOfClass:[GMNavigationController class]]) {
return (GMNavigationController *)rootVC;
} else {
return nil;
}
}
#pragma mark - 判断用户是否允许推送
+ (void)userIsAllowPush:(void (^)(BOOL))isOpenPushBlock {
if (@available(iOS 10.0, *)) {
[[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
dispatch_async(dispatch_get_main_queue(), ^{
if (settings.authorizationStatus == UNAuthorizationStatusNotDetermined){
//未选择---没有选择允许或者不允许,按不允许处理
isOpenPushBlock(NO);
} else if (settings.authorizationStatus == UNAuthorizationStatusDenied){
//未授权--不允许推送
isOpenPushBlock(NO);
} else if (settings.authorizationStatus == UNAuthorizationStatusAuthorized){
//已授权--允许推送
isOpenPushBlock(YES);
}
});
}];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
UIUserNotificationSettings *settings = [[UIApplication sharedApplication] currentUserNotificationSettings];
BOOL isAllow = (settings.types != UIUserNotificationTypeNone);
isOpenPushBlock(isAllow);
});
}
}
@end
//
// GMCalendarEventManager.swift
// Gengmei
//
// Created by 汪俊 on 16/7/4.
// Copyright © 2016年 更美互动信息科技有限公司. All rights reserved.
//
import UIKit
//import GMBaseSwift
import EventKit
public typealias CalendarAuthorizationAllowedBlock = () -> Void
@objcMembers
public class GMCalendarEventManager: NSObject {
public var authorizationAllowed: CalendarAuthorizationAllowedBlock?
public var eventStore = EKEventStore()
public override init() {
super.init()
//请求授权
let status = EKEventStore.authorizationStatus(for: .event)
if status == .notDetermined {
eventStore.requestAccess(to: .event, completion: { (allowed, _) in
if allowed {
print("allowed")
if self.authorizationAllowed != nil {
self.authorizationAllowed!()
}
} else {
print("not allowed")
}
})
} else if status == .restricted || status == .denied {
showAlertcontrolle()
}
}
/**
添加日历事件
- author: wangjun 07-07-2016 in 6.1.0
- parameter title: 事件的标题
- parameter startDate: 开始时间
- parameter endDate: 结束时间`
- parameter triggerTime: 提醒事件
- parameter url: 跳转到美购详情页面的地址
*/
public func addEvent(_ title: String = "", startDate: TimeInterval, endDate: TimeInterval, serviceId: String, url: String) {
let timeZone = TimeZone.current
let deltaTime = timeZone.secondsFromGMT()
let event = EKEvent(eventStore: eventStore)
let gengmeiTitle = "更美App秒杀: " + title
event.title = gengmeiTitle
event.startDate = Date(timeIntervalSince1970: startDate - TimeInterval(deltaTime))
event.endDate = Date(timeIntervalSince1970: endDate - TimeInterval(deltaTime))
event.calendar = eventStore.defaultCalendarForNewEvents
event.notes = url
//计算出秒杀提醒时间,开始前3分钟
var triggerTime = Date(timeIntervalSince1970: startDate)
triggerTime = triggerTime.addingTimeInterval(-180 - TimeInterval(deltaTime))
let alarm = EKAlarm(absoluteDate: triggerTime)
event.addAlarm(alarm)
do {
try eventStore.save(event, span: .thisEvent)
//将event的id 保存到本地
UserDefaults.standard.set(event.eventIdentifier, forKey: serviceId)
UserDefaults.standard.synchronize()
} catch {
print("error show")
}
}
/**
通过标题删除日历事件
- author: wangjun 07-07-2016 in 6.1.0
- parameter title: 事件的标题
*/
public func removeEvent(_ serviceId: String) {
//取出对应的event eventId
guard let eventId = (UserDefaults.standard.object(forKey: serviceId)) as? String else {
return
}
let event = eventStore.event(withIdentifier: eventId)
do {
if event != nil {
try eventStore.remove(event!, span: .thisEvent)
}
} catch {
print("error")
}
}
/**
判断用户是够开启日历服务
- author: wangjun
- date: 16-07-04 18:07:22 07-04-2016 in 6.1.0
*/
public func checkAuthorized() -> Bool {
return EKEventStore.authorizationStatus(for: .event) == .authorized
}
/**
提醒用户开启日历服务的alert
- author: wangjun 07-07-2016 in 6.1.0
*/
public func showAlertcontrolle() {
let status = EKEventStore.authorizationStatus(for: .event)
if status == .authorized {
return
} else {
let alert = UIAlertController(title: "", message: "未授权日历权限,可能导致无法成功提醒哟,快去开启设置吧", preferredStyle: .alert)
let actionA = UIAlertAction(title: "取消", style: .cancel) { (_) in}
alert.addAction(actionA)
let actionB = UIAlertAction(title: "去设置", style: .default) { (_) in
if #available(iOS 10.0, *) {
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(URL(string: UIApplication.openSettingsURLString)!)
}
}
alert.addAction(actionB)
GMBaseTool.getCurrentViewController().present(alert, animated: true, completion: nil)
}
}
}
//
// GMFloatWindow.h
// Gengmei
//
// Created by licong on 2018/1/25.
// Copyright © 2018年 更美互动信息科技有限公司. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface GMFloatWindow : UIView <UIGestureRecognizerDelegate>
//标记当前直播/回放页面是否是浮窗状态还是全屏状态, true -> 全屏, false -> 浮窗
@property (nonatomic, assign) BOOL isExpanded;
@property (nonatomic, assign) BOOL isShowingAnimation;
@property (nonatomic, strong) UITapGestureRecognizer *tapGesture;
- (void)showController:(UIViewController *)controller;
- (void)hide;
- (void)hideWithNoAnimation;
- (void)zoomToSmall;
- (void)videoZoomToSmall;
- (void)expandAction:(UITapGestureRecognizer *)tapGesture;
@end
//
// GMFloatWindow.m
// Gengmei
//
// Created by licong on 2018/1/25.
// Copyright © 2018年 更美互动信息科技有限公司. All rights reserved.
//
#import "GMFloatWindow.h"
//#import "GMLoginManager.h"
//#import "UITabBar+ViewConstant.h"
#import "GMFloatWindowProtocol.h"
#import "GMBaseTool.h"
@import GMKit;
@interface GMFloatWindow()<CAAnimationDelegate> {
UIViewController *_controller;
}
@property (nonatomic, strong) UIPanGestureRecognizer *panGesture;
@end
@implementation GMFloatWindow
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:[UIScreen mainScreen].bounds];
if (self) {
self.clipsToBounds = YES;
self.backgroundColor = [UIColor clearColor];
_tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(expandAction:)];
_tapGesture.delegate = self;
_panGesture =[[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panAction:)];
_panGesture.minimumNumberOfTouches = 1;
_panGesture.maximumNumberOfTouches = 1;
_panGesture.delegate = self;
self.isExpanded = YES;
}
return self;
}
- (void)showController:(UIViewController *)controller {
if (self.superview == nil) {
[[UIApplication sharedApplication].keyWindow addSubview:self];
// [[AppDelegate shareInstance].window addSubview:self];
}
_controller = controller;
[self addSubview:controller.view];
self.isShowingAnimation = YES;
if (_isExpanded) {
//当前没有小浮窗
//transform 要设置在前面因为它会印象frame
self.transform = CGAffineTransformMakeScale(1, 1);
self.frame = CGRectMake(0, MAINSCREEN_HEIGHT, MAINSCREEN_WIDTH, MAINSCREEN_HEIGHT);
self.layer.cornerRadius = 0;
self.hidden = NO;
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
self.top = 0;
} completion:^(BOOL finished) {
self.isShowingAnimation = NO;
}];
}else {
//当前已经有小浮窗,需要关闭当前小浮窗,在打开新的浮窗,具体实现其实是换了window的rootCtroller
//先移除手势
[self addGestureRecognizer:self.tapGesture];
[self addGestureRecognizer:self.panGesture];
//去掉圆角
self.layer.cornerRadius = 0;
//window大小还原,将window刚好放在屏幕下面
self.transform = CGAffineTransformMakeScale(1, 1);
self.frame = CGRectMake(0, MAINSCREEN_HEIGHT, MAINSCREEN_WIDTH, MAINSCREEN_HEIGHT);
[UIView animateWithDuration:0.25 delay:0.1 options:UIViewAnimationOptionCurveEaseIn animations:^{
self.top = 0;
} completion:^(BOOL finished) {
self.isShowingAnimation = NO;
}];
}
}
- (void)hideWithNoAnimation{
self.hidden = YES;
//恢复成默认值
self.isExpanded = YES;
}
- (void)hide {
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
self.top = MAINSCREEN_HEIGHT;
} completion:^(BOOL finished) {
[self hideWithNoAnimation];
_controller = nil;
}];
}
- (void)expandAction:(UITapGestureRecognizer *)tapGesture{
[[GMBaseTool getCurrentViewController].view endEditing:YES];
self.layer.cornerRadius = 0;
self.hidden = NO;
[UIView animateWithDuration:0.25f animations:^{
self.transform = CGAffineTransformMakeScale(1, 1);
self.frame = CGRectMake(0, 0, MAINSCREEN_WIDTH, MAINSCREEN_HEIGHT);
} completion:^(BOOL finished) {
[self removeGestureRecognizer:self.tapGesture];
[self removeGestureRecognizer:self.panGesture];
}];
[(id<GMFloatWindowProtocol>)_controller showOtherViews];
_isExpanded = YES;
}
-(void)panAction:(UIPanGestureRecognizer *)recognizer {
UIWindow *window = [UIApplication sharedApplication].keyWindow;
CGPoint translatedPoint = [recognizer translationInView:window];
switch (recognizer.state) {
case UIGestureRecognizerStateBegan:
{
break;
}
case UIGestureRecognizerStateChanged:
{
CGFloat x = recognizer.view.center.x + translatedPoint.x;
CGFloat y = recognizer.view.center.y + translatedPoint.y;
if (x < recognizer.view.width / 2.0) {
x = recognizer.view.width / 2.0;
} else if (x + recognizer.view.width / 2.0 > MAINSCREEN_WIDTH) {
x = MAINSCREEN_WIDTH - recognizer.view.width / 2.0;
}
if (y < recognizer.view.height / 2.0) {
y = recognizer.view.height / 2.0;
} else if (y + recognizer.view.height / 2.0> MAINSCREEN_HEIGHT) {
y = MAINSCREEN_HEIGHT - recognizer.view.height / 2.0;
}
recognizer.view.center = CGPointMake(x, y);
UIWindow *window = [UIApplication sharedApplication].keyWindow;
[recognizer setTranslation:CGPointMake(0, 0) inView:window];
break;
}
case UIGestureRecognizerStateCancelled:
case UIGestureRecognizerStateEnded:
default:
break;
}
}
- (void)zoomToSmall {
if (_controller == nil) {
return;
}
[(id<GMFloatWindowProtocol>)_controller hideOtherViews];
self.layer.cornerRadius = 18;
_isExpanded = NO;
[UIView animateWithDuration:0.25f animations:^{
self.transform = CGAffineTransformMakeScale(0.35, 0.35);
self.frame = CGRectMake(MAINSCREEN_WIDTH * 0.65, 85, MAINSCREEN_WIDTH * 0.35, MAINSCREEN_HEIGHT * 0.35);
} completion:^(BOOL finished) {
[self addGestureRecognizer:self.tapGesture];
[self addGestureRecognizer:self.panGesture];
}];
}
// 视频缩小,宽是屏幕1/4,宽高比3:5
- (void)videoZoomToSmall {
if (_controller == nil) {
return;
}
[(id<GMFloatWindowProtocol>)_controller hideOtherViews];
self.layer.cornerRadius = 28;
_isExpanded = NO;
[UIView animateWithDuration:0.25f animations:^{
self.transform = CGAffineTransformMakeScale(0.35, 0.35);
self.frame = CGRectMake(MAINSCREEN_WIDTH * 0.75, MAINSCREEN_HEIGHT * 0.16, MAINSCREEN_WIDTH * 0.25, MAINSCREEN_WIDTH * 0.417);
} completion:^(BOOL finished) {
[self addGestureRecognizer:self.tapGesture];
[self addGestureRecognizer:self.panGesture];
}];
}
#pragma mark- Gesture Delagate
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
@end
//
// GMFloatWindowProtocol.h
// Gengmei
//
// Created by wangyang on 2018/5/2.
// Copyright © 2018年 更美互动信息科技有限公司. All rights reserved.
//
#import <Foundation/Foundation.h>
@protocol GMFloatWindowProtocol <NSObject>
/**
GMFloatWindow 由全屏模式到小窗口模式,会调用此方法
*/
- (void)hideOtherViews;
/**
GMFloatWindow 由小窗口模式到全屏模式,会调用此方法
*/
- (void)showOtherViews;
@end
//
// GMLiveLikeView.h
// Gengmei
//
// Created by licong on 2017/1/6.
// Copyright © 2017年 更美互动信息科技有限公司. All rights reserved.
//
#import <UIKit/UIKit.h>
//直播点赞按钮
typedef void(^LiveLikeClickedBlock)(void);
@interface GMLiveLikeView : UIView
@property (nonatomic, copy) LiveLikeClickedBlock liveLikeClickedBlock;
@end
//
// GMLiveLikeView.m
// Gengmei
//
// Created by licong on 2017/1/6.
// Copyright © 2017年 更美互动信息科技有限公司. All rights reserved.
//
#import "GMLiveLikeView.h"
#import "GMButton.h"
@import GMKit;
@interface GMLiveLikeView ()
@property (strong ,nonatomic) GMButton *button;
@end
@implementation GMLiveLikeView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
- (void)setup{
_button = [GMButton buttonWithType:UIButtonTypeCustom];
_button.frame = self.bounds;
_button.enableAdaptive = YES;
_button.adjustsImageWhenHighlighted = NO;
[_button setImage:[UIImage imageNamed:@"live_heart_nor"] forState:UIControlStateNormal];
[_button addTarget:self action:@selector(likeButtonClicked) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_button];
}
- (void)layoutSubviews{
[super layoutSubviews];
_button.frame = self.bounds;
}
- (void)likeButtonClicked{
UIView * gradientView = [[UIView alloc]
initWithFrame:self.bounds];
gradientView.frame = self.bounds;
gradientView.layer.cornerRadius = self.frame.size.height/2;
gradientView.userInteractionEnabled = NO;
gradientView.center = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
gradientView.layer.cornerRadius = self.frame.size.height/2;
gradientView.backgroundColor = RGBCOLOR_HEX(0xFF7690);
gradientView.hidden = YES;
[self addSubview:gradientView];
gradientView.alpha = 1;
gradientView.hidden = NO;
gradientView.transform = CGAffineTransformMakeScale(1, 1);
[_button setImage:[UIImage imageNamed:@"live_heart_pre"] forState:UIControlStateNormal];
[UIView animateWithDuration:0.3
animations:^{
gradientView.alpha = 0.3;
gradientView.transform = CGAffineTransformMakeScale(2, 2);
}completion:^(BOOL finish){
[gradientView removeFromSuperview];
[_button setImage:[UIImage imageNamed:@"live_heart_nor"] forState:UIControlStateNormal];
}];
if (self.liveLikeClickedBlock) {
self.liveLikeClickedBlock();
}
}
@end
//
// WMLocationManager.h
// Gengmei
//
// Created by wangyang on 5/6/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#define kLastLocation @"lastLocation"
typedef void(^LocationFinishBlock)(CLLocation *location);
/**
保证新用户在有网络的情况下弹出 授权框 不然调不起地理位置弹框
*/
//typedef void(^RequestLocationAuthorization)();
@interface GMLocationManager : NSObject <CLLocationManagerDelegate>
@property (nonatomic, readonly) CLLocation *location;
@property (nonatomic, assign, readonly) NSInteger statusWhenAppBackground;
+ (instancetype)shareInstance;
/**
* @author wangyang, 16-03-11 17:03:21
*
* @brief 定位成功的回调
* @since 5.9.2
*/
- (void)addBlock:(LocationFinishBlock)block;
/**
* @author wangyang, 16-03-11 17:03:31
*
* @brief 在dealloc时需要手动移除先前添加的block
* @since 5.9.2
*/
- (void)removeLast;
/**
* @author wangyang, 16-02-24 15:02:11
*
* @brief 尝试使用定位功能。如果没有权限会有提示,如果有权限直接重新定位
* @since 5.9.1
*/
- (void)tryLocationService;
//@property (nonatomic, copy) RequestLocationAuthorization *authorizationBlock;
/**
保证有网络的l时候调用 不然那会失败
*/
-(void)notDeterminedShowLocationAuthorize;
/**
尝试使用定位功能。如果没有权限会有提示,并只提示一次
@param key 如果key不为nil,那么该key下的无权限提示只会展示一次,直到app删除。
如果key为nil,相当于调用tryLocationService
*/
- (void)tryLocationServiceForKey:(NSString *)key;
/**
* @author wangyang, 16-03-10 14:03:57
*
* @brief 静默的尝试重新定位,而不会有任何提示
* @since 5.9.2
*/
- (void)updateLocation;
/**
* @author wangyang, 16-03-10 12:03:57
*
* @brief 会判断是否允许定位服务
* @return YES表示允许
* @since 5.9.2
*/
+ (BOOL)hasOpenLocationService;
/**
* @author wangyang, 16-03-10 18:03:03
*
* @brief 跳转到定位服务的系统设置页
* @since 5.9.2
*/
+ (void)jumpToSettings;
/**
* @author wangyang, 16-03-10 12:03:21
*
* @brief 会判断是否允许定位服务,如果不允许会弹出alert
* @since 5.9.2
*/
+ (void)showAlertIfNotAllowed;
@end
//
// WMLocationManager.m
// Gengmei
//
// Created by wangyang on 5/6/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "GMLocationManager.h"
#import "JZLocationConverter.h"
#import <GMPhobos/Phobos.h>
#import <GMPhobos/UIResponder+PhobosPV.h>
#import "GMBaseTool.h"
@import GMFoundation;
@import GMCache;
@import GMKit;
@interface GMLocationManager ()<CLLocationManagerDelegate>{
NSMutableArray<LocationFinishBlock> *_blocks;
// 因为定位回调会走多次,使用该变量可以设定为只使用第一次返回的数据
BOOL _firstTime;
}
@property (nonatomic, strong) CLLocationManager *locationManager;
@property (nonatomic, strong) CLLocation *location;
@property (nonatomic, assign) NSInteger statusWhenAppBackground;
@end
@implementation GMLocationManager
+ (instancetype)shareInstance
{
static dispatch_once_t onceToken;
static id instance = nil;
dispatch_once(&onceToken, ^{
instance = [[[self class] alloc] init];
});
return instance;
}
- (instancetype)init
{
self = [super init];
if (self) {
_blocks = [NSMutableArray array];
// 实例化一个位置管理器
self.locationManager = [[CLLocationManager alloc] init];
[self.locationManager setDelegate:self];
// 设置定位精度
// kCLLocationAccuracyNearestTenMeters:精度10米
// kCLLocationAccuracyHundredMeters:精度100 米
// kCLLocationAccuracyKilometer:精度1000 米
// kCLLocationAccuracyThreeKilometers:精度3000米
// kCLLocationAccuracyBest:设备使用电池供电时候最高的精度
// kCLLocationAccuracyBestForNavigation:导航情况下最高精度,一般要有外接电源时才能使用
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
// distanceFilter是距离过滤器,为了减少对定位装置的轮询次数,位置的改变不会每次都去通知委托,而是在移动了足够的距离时才通知委托程序
// 它的单位是米,这里设置为至少移动1000再通知委托处理更新;
self.locationManager.distanceFilter = 1000.0f;// 如果设为kCLDistanceFilterNone,则每秒更新一次;
// 初始化
// _location = [[CLLocation alloc] initWithLatitude:0 longitude:0];
self.locationManager.pausesLocationUpdatesAutomatically = NO;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
_firstTime = YES;
if ([GMLocationManager hasOpenLocationService]) {
[self updateLocation];
}
}
return self;
}
- (void)applicationDidEnterBackground {
self.statusWhenAppBackground = [CLLocationManager authorizationStatus];
}
#pragma mark - Method
/**
* @author wangyang, 16-03-10 12:03:57
*
* @brief 会判断是否允许定位服务
* @return YES表示允许
* @since 5.9.2
*/
+ (BOOL)hasOpenLocationService {
CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
if(status == kCLAuthorizationStatusAuthorizedAlways || status == kCLAuthorizationStatusAuthorizedWhenInUse){
return YES;
}else{
return NO;
}
}
/**
* @author wangyang, 16-02-24 15:02:11
*
* @brief 尝试使用定位功能。如果没有权限会有提示,如果有权限直接重新定位
* @since 5.9.1
*/
- (void)tryLocationService {
[GMLocationManager showAlertIfNotAllowed];
[self requestAuthorization];
}
- (void)tryLocationServiceForKey:(NSString *)key {
[GMLocationManager showAlertIfNotAllowedForKey:key];
[self requestAuthorization];
}
+ (void)showAlertIfNotAllowed {
[self showAlertIfNotAllowedForKey:nil];
}
/**
* @author wangyang, 16-03-10 12:03:21
*
* @brief 会判断是否允许定位服务,如果不允许会弹出alert
* @since 5.9.2
*/
+ (void)showAlertIfNotAllowedForKey:(NSString *)key {
if (key != nil) {
NSString *value = [GMCache fetchObjectAtDocumentPathWithkey:key];
if (value != nil) {
return;
}
}
CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
NSString *title = nil;
NSString *detail = nil;
NSString *cancelButton = @"好的";
NSString *otherButton = nil;
if (status == kCLAuthorizationStatusRestricted) {
title = @"无法使用定位服务";
} else if(status == kCLAuthorizationStatusDenied) {
title = @"定位服务已经关闭";
detail = @"请在设置中确保定位服务开启,并且允许该程序访问地理位置";
otherButton = @"去设置";
cancelButton = @"暂不";
}
// 如果用户关闭了定位服务,或者某种未预料到的原因使title总不为空
if ([title isNonEmpty]) {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:detail preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:cancelButton style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
if (key != nil) {
[GMCache storeObjectAtDocumentPathWithkey:key object:key];
}
}]];
if ([otherButton isNonEmpty]) {
[alert addAction:[UIAlertAction actionWithTitle:otherButton style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[GMLocationManager jumpToSettings];
if (key != nil) {
[GMCache storeObjectAtDocumentPathWithkey:key object:key];
}
}]];
}
// 虽然选择城市页是被present的,但是 visibleViewController 仍然能访问到该controller
[[GMBaseTool getCurrentViewController] presentViewController:alert animated:YES completion:NULL];
}
}
+ (void)jumpToSettings {
CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
if (status == kCLAuthorizationStatusNotDetermined) {
[[GMLocationManager shareInstance] tryLocationService];
return;
}
// iOS8及以后可以跳转到该app的专有设置页
NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"10.0")) {
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:NULL];
} else {
[[UIApplication sharedApplication] openURL:url];
}
}
/**
* @author wangyang, 16-02-24 15:02:30
*
* @brief 以请求相应权限。如果定位服务异常不做任何处理,因为有相应的需求来处理这定位异常
* @since 5.9.1
*/
- (void)requestAuthorization{
CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
if(status == kCLAuthorizationStatusNotDetermined){
// 弹出是否允许使用位置服务 要保证有网络的情况下弹出 请求 不然会报错
// [self.locationManager requestWhenInUseAuthorization];
[self _updatingLocation];
} else if (status == kCLAuthorizationStatusAuthorizedAlways || status == kCLAuthorizationStatusAuthorizedWhenInUse){
[self _updatingLocation];
}
}
- (void)notDeterminedShowLocationAuthorize {
CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
if (status == kCLAuthorizationStatusNotDetermined) {
[self.locationManager requestWhenInUseAuthorization];
[Phobos track:@"popup_view" attributes:@{@"page_name": SafeString([GMBaseTool getCurrentViewController].pageName), @"popup_name": @"location"} sendNow:YES];
[self _updatingLocation];
} else {
[self _updatingLocation];
}
}
- (void)updateLocation {
CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
BOOL enable = [CLLocationManager locationServicesEnabled];
if (enable && (status == kCLAuthorizationStatusAuthorizedAlways || status == kCLAuthorizationStatusAuthorizedWhenInUse)) {
[self _updatingLocation];
}
}
- (void)_updatingLocation {
[self.locationManager startUpdatingLocation];
_firstTime = YES;
}
#pragma mark - CLLocationManagerDelegate
// 地理位置发生改变时触发
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations {
// 注意此处可能会被多次调用,这是CoreLocation中的正常现象
self.location = locations.lastObject;
[self finishUpdatingLocation];
}
// 定位失误时触发
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
// self.location = [[CLLocation alloc] initWithLatitude:0 longitude:0];
[self finishUpdatingLocation];
if (error.code == kCLErrorLocationUnknown ) {
NSLog(@"获取失败,会继续尝试获取位置信息");
}
}
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
// 如果不允许访问地理位置,直接完成
if (status == kCLAuthorizationStatusRestricted || status == kCLAuthorizationStatusDenied) {
[self finishUpdatingLocation];
[Phobos track:@"report_status" attributes:@{@"page_name": SafeString([GMBaseTool getCurrentViewController].pageName), @"report_name":@"close_location"} sendNow:YES];
}else if (status == kCLAuthorizationStatusAuthorizedAlways || status == kCLAuthorizationStatusAuthorizedWhenInUse){
[self _updatingLocation];
[Phobos track:@"report_status" attributes:@{@"page_name": SafeString([GMBaseTool getCurrentViewController].pageName), @"report_name":@"open_location"} sendNow:YES];
}
}
- (void)finishUpdatingLocation {
for (int i = 0; i < _blocks.count; i ++) {
LocationFinishBlock block = _blocks[i];
block(self.location);
}
}
// iOS返回的是国际坐标,需要转换为百度坐标
- (void)setLocation:(CLLocation *)location {
if (location == nil) {
return;
}
CLLocationCoordinate2D coordinate = [JZLocationConverter wgs84ToBd09:location.coordinate];
// ZX 不开启定位时,不需要传经纬度 经过上边方法转换后有小误差,手动置为0
if (![GMLocationManager hasOpenLocationService]) {
coordinate.latitude = 0;
coordinate.longitude = 0;
}
_location = [[CLLocation alloc] initWithLatitude:coordinate.latitude longitude:coordinate.longitude];
[GMCache storeObjectAtDiskWithkey:kLastLocation object:_location];
}
#pragma mark - Block Manage
- (void)addBlock:(LocationFinishBlock)block {
[_blocks addObject:block];
}
- (void)removeLast {
[_blocks removeLastObject];
}
@end
//
// GMRountClassManager.h
// GMBase
//
// Created by 卢悦明 on 2020/5/12.
//
#import <Foundation/Foundation.h>
@interface GMRountClassManager : NSObject
/// 通过key找到对应的方法,回调想要的值
/// @param key
- (id)getMassge:(NSString *)key;
/// 调用方法
- (void)sendCladdMethod:(NSString *)key;
@end
//
// GMRountClassManager.m
// GMBase
//
// Created by 卢悦明 on 2020/5/12.
//
#import "GMRountClassManager.h"
@implementation GMRountClassManager
@end
//
// GMWelfareServiceObject.h
// GMBase
//
// Created by 卢悦明 on 2020/5/12.
//
#import <GMBase/GMObject.h>
#import "GMWelfareVideObject.h"
typedef NS_ENUM(NSUInteger, GMWelfareDetailServiceType) {
GMWelfareDetailServiceTypeDefault = 1,// 其他(后端也不知道1, 2啥意思, 旗舰店之前客户端没用到)
GMWelfareDetailServiceTypeFlagShip = 3,// 旗舰店(新加类型)
};
NS_ASSUME_NONNULL_BEGIN
@protocol GMWelfareServiceObject @end
@interface GMWelfareServiceObject : GMObject
@property (nonatomic, strong) NSString *serviceId;
@property (nonatomic, strong) NSString *serviceName;
@property (nonatomic, strong) NSString *shortDescription;
@property (nonatomic, strong) NSString *doctorName;
@property (nonatomic, assign) BOOL doctorRecommend;
@property (nonatomic, strong) NSString *pic;
@property (nonatomic, strong) NSString *picSmall;
/** 划线价(7745开始,未选中sku时,会传一个价格区间), 不需要展示的,original_price会传空字符串 */
@property (nonatomic, strong) NSString *originalPrice;
@property (nonatomic, strong) NSString *gengmeiPrice;
@property (nonatomic, strong) NSString *picNum;
@property (nonatomic, assign) NSInteger sellAmount;
@property (nonatomic, strong) NSString *city;
@property (nonatomic, strong) NSString *url;
@property (nonatomic, strong) NSString *specialInfo;
@property (nonatomic, strong) NSString *specialId;
@property (nonatomic, strong) NSString *specialInfoUrl;
@property (nonatomic, assign) NSInteger saleLimit;
@property (nonatomic, strong) NSNumber *commentRate;
@property (nonatomic, strong) NSArray <GMWelfareVideObject> *headerPic;
@property (nonatomic, strong) NSString *distance;
/** 添加提问按钮 的列表问题 从AI过来的时候, 会有这个字段 by add 7.22.0*/
@property (nonatomic, copy) NSString *projectTag;
/** 美购类型, 先判断旗舰店, 否则就是其他 */
@property (nonatomic, assign) GMWelfareDetailServiceType serviceType;
@property (nonatomic, copy) NSString *image;
@end
NS_ASSUME_NONNULL_END
//
// GMWelfareServiceObject.m
// GMBase
//
// Created by 卢悦明 on 2020/5/12.
//
#import "GMWelfareServiceObject.h"
@implementation GMWelfareServiceObject
@end
//
// GMWelfareVideObject.h
// GMBase
//
// Created by 卢悦明 on 2020/5/12.
//
#import <GMBase/GMObject.h>
NS_ASSUME_NONNULL_BEGIN
@protocol GMWelfareVideObject @end
@interface GMWelfareVideObject : GMObject
@property (nonatomic, strong) NSString *pic;
@property (nonatomic, strong) NSString *videoUrl;
@end
NS_ASSUME_NONNULL_END
//
// GMWelfareVideObject.m
// GMBase
//
// Created by 卢悦明 on 2020/5/12.
//
#import "GMWelfareVideObject.h"
@implementation GMWelfareVideObject
@end
//
// GMZoneTagsObject.h
// Gengmei
//
// Created by 卢悦明 on 2020/5/12.
// Copyright © 2020 更美互动信息科技有限公司. All rights reserved.
//
#import <GMBase/GMObject.h>
NS_ASSUME_NONNULL_BEGIN
@protocol GMZoneTagsObject @end
@interface GMZoneTagsObject : GMObject
@property (nonatomic, assign) NSInteger id;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *tagType;
/** 是否是新标签 */
@property (nonatomic, assign) BOOL isNew;
@end
NS_ASSUME_NONNULL_END
//
// GMZoneTagsObject.m
// Gengmei
//
// Created by 卢悦明 on 2020/5/12.
// Copyright © 2020 更美互动信息科技有限公司. All rights reserved.
//
#import "GMZoneTagsObject.h"
@implementation GMZoneTagsObject
@end
//
// JZLocationConverter.h
// Gengmei
//
// Created by wangyang on 2017/8/15.
// Copyright © 2017年 更美互动信息科技有限公司. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
@interface JZLocationConverter : NSObject
/**
* @brief 世界标准地理坐标(WGS-84) 转换成 中国国测局地理坐标(GCJ-02)<火星坐标>
*
* ####只在中国大陆的范围的坐标有效,以外直接返回世界标准坐标
*
* @param location 世界标准地理坐标(WGS-84)
*
* @return 中国国测局地理坐标(GCJ-02)<火星坐标>
*/
+ (CLLocationCoordinate2D)wgs84ToGcj02:(CLLocationCoordinate2D)location;
/**
* @brief 中国国测局地理坐标(GCJ-02) 转换成 世界标准地理坐标(WGS-84)
*
* ####此接口有1-2米左右的误差,需要精确定位情景慎用
*
* @param location 中国国测局地理坐标(GCJ-02)
*
* @return 世界标准地理坐标(WGS-84)
*/
+ (CLLocationCoordinate2D)gcj02ToWgs84:(CLLocationCoordinate2D)location;
/**
* @brief 世界标准地理坐标(WGS-84) 转换成 百度地理坐标(BD-09)
*
* @param location 世界标准地理坐标(WGS-84)
*
* @return 百度地理坐标(BD-09)
*/
+ (CLLocationCoordinate2D)wgs84ToBd09:(CLLocationCoordinate2D)location;
/**
* @brief 中国国测局地理坐标(GCJ-02)<火星坐标> 转换成 百度地理坐标(BD-09)
*
* @param location 中国国测局地理坐标(GCJ-02)<火星坐标>
*
* @return 百度地理坐标(BD-09)
*/
+ (CLLocationCoordinate2D)gcj02ToBd09:(CLLocationCoordinate2D)location;
/**
* @brief 百度地理坐标(BD-09) 转换成 中国国测局地理坐标(GCJ-02)<火星坐标>
*
* @param location 百度地理坐标(BD-09)
*
* @return 中国国测局地理坐标(GCJ-02)<火星坐标>
*/
+ (CLLocationCoordinate2D)bd09ToGcj02:(CLLocationCoordinate2D)location;
/**
* @brief 百度地理坐标(BD-09) 转换成 世界标准地理坐标(WGS-84)
*
* ####此接口有1-2米左右的误差,需要精确定位情景慎用
*
* @param location 百度地理坐标(BD-09)
*
* @return 世界标准地理坐标(WGS-84)
*/
+ (CLLocationCoordinate2D)bd09ToWgs84:(CLLocationCoordinate2D)location;
@end
//
// JZLocationConverter.m
// Gengmei
//
// Created by wangyang on 2017/8/15.
// Copyright © 2017年 更美互动信息科技有限公司. All rights reserved.
//
#import "JZLocationConverter.h"
@implementation JZLocationConverter
#define LAT_OFFSET_0(x,y) -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * sqrt(fabs(x))
#define LAT_OFFSET_1 (20.0 * sin(6.0 * x * M_PI) + 20.0 * sin(2.0 * x * M_PI)) * 2.0 / 3.0
#define LAT_OFFSET_2 (20.0 * sin(y * M_PI) + 40.0 * sin(y / 3.0 * M_PI)) * 2.0 / 3.0
#define LAT_OFFSET_3 (160.0 * sin(y / 12.0 * M_PI) + 320 * sin(y * M_PI / 30.0)) * 2.0 / 3.0
#define LON_OFFSET_0(x,y) 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * sqrt(fabs(x))
#define LON_OFFSET_1 (20.0 * sin(6.0 * x * M_PI) + 20.0 * sin(2.0 * x * M_PI)) * 2.0 / 3.0
#define LON_OFFSET_2 (20.0 * sin(x * M_PI) + 40.0 * sin(x / 3.0 * M_PI)) * 2.0 / 3.0
#define LON_OFFSET_3 (150.0 * sin(x / 12.0 * M_PI) + 300.0 * sin(x / 30.0 * M_PI)) * 2.0 / 3.0
#define X_PI ((M_PI * 3000.0) / 180.0)
#define RANGE_LON_MAX 137.8347
#define RANGE_LON_MIN 72.004
#define RANGE_LAT_MAX 55.8271
#define RANGE_LAT_MIN 0.8293
// jzA = 6378245.0, 1/f = 298.3
// b = a * (1 - f)
// ee = (a^2 - b^2) / a^2;
#define jzA 6378245.0
#define jzEE 0.00669342162296594323
+ (double)transformLat:(double)x bdLon:(double)y
{
double ret = LAT_OFFSET_0(x, y);
ret += LAT_OFFSET_1;
ret += LAT_OFFSET_2;
ret += LAT_OFFSET_3;
return ret;
}
+ (double)transformLon:(double)x bdLon:(double)y
{
double ret = LON_OFFSET_0(x, y);
ret += LON_OFFSET_1;
ret += LON_OFFSET_2;
ret += LON_OFFSET_3;
return ret;
}
+ (BOOL)outOfChina:(double)lat bdLon:(double)lon
{
if (lon < RANGE_LON_MIN || lon > RANGE_LON_MAX)
return true;
if (lat < RANGE_LAT_MIN || lat > RANGE_LAT_MAX)
return true;
return false;
}
+ (CLLocationCoordinate2D)gcj02Encrypt:(double)ggLat bdLon:(double)ggLon
{
CLLocationCoordinate2D resPoint;
double mgLat;
double mgLon;
if ([self outOfChina:ggLat bdLon:ggLon]) {
resPoint.latitude = ggLat;
resPoint.longitude = ggLon;
return resPoint;
}
double dLat = [self transformLat:(ggLon - 105.0)bdLon:(ggLat - 35.0)];
double dLon = [self transformLon:(ggLon - 105.0) bdLon:(ggLat - 35.0)];
double radLat = ggLat / 180.0 * M_PI;
double magic = sin(radLat);
magic = 1 - jzEE * magic * magic;
double sqrtMagic = sqrt(magic);
dLat = (dLat * 180.0) / ((jzA * (1 - jzEE)) / (magic * sqrtMagic) * M_PI);
dLon = (dLon * 180.0) / (jzA / sqrtMagic * cos(radLat) * M_PI);
mgLat = ggLat + dLat;
mgLon = ggLon + dLon;
resPoint.latitude = mgLat;
resPoint.longitude = mgLon;
return resPoint;
}
+ (CLLocationCoordinate2D)gcj02Decrypt:(double)gjLat gjLon:(double)gjLon {
CLLocationCoordinate2D gPt = [self gcj02Encrypt:gjLat bdLon:gjLon];
double dLon = gPt.longitude - gjLon;
double dLat = gPt.latitude - gjLat;
CLLocationCoordinate2D pt;
pt.latitude = gjLat - dLat;
pt.longitude = gjLon - dLon;
return pt;
}
+ (CLLocationCoordinate2D)bd09Decrypt:(double)bdLat bdLon:(double)bdLon
{
CLLocationCoordinate2D gcjPt;
double x = bdLon - 0.0065, y = bdLat - 0.006;
double z = sqrt(x * x + y * y) - 0.00002 * sin(y * X_PI);
double theta = atan2(y, x) - 0.000003 * cos(x * X_PI);
gcjPt.longitude = z * cos(theta);
gcjPt.latitude = z * sin(theta);
return gcjPt;
}
+(CLLocationCoordinate2D)bd09Encrypt:(double)ggLat bdLon:(double)ggLon
{
CLLocationCoordinate2D bdPt;
double x = ggLon, y = ggLat;
double z = sqrt(x * x + y * y) + 0.00002 * sin(y * X_PI);
double theta = atan2(y, x) + 0.000003 * cos(x * X_PI);
bdPt.longitude = z * cos(theta) + 0.0065;
bdPt.latitude = z * sin(theta) + 0.006;
return bdPt;
}
+ (CLLocationCoordinate2D)wgs84ToGcj02:(CLLocationCoordinate2D)location
{
return [self gcj02Encrypt:location.latitude bdLon:location.longitude];
}
+ (CLLocationCoordinate2D)gcj02ToWgs84:(CLLocationCoordinate2D)location
{
return [self gcj02Decrypt:location.latitude gjLon:location.longitude];
}
+ (CLLocationCoordinate2D)wgs84ToBd09:(CLLocationCoordinate2D)location
{
CLLocationCoordinate2D gcj02Pt = [self gcj02Encrypt:location.latitude
bdLon:location.longitude];
return [self bd09Encrypt:gcj02Pt.latitude bdLon:gcj02Pt.longitude] ;
}
+ (CLLocationCoordinate2D)gcj02ToBd09:(CLLocationCoordinate2D)location
{
return [self bd09Encrypt:location.latitude bdLon:location.longitude];
}
+ (CLLocationCoordinate2D)bd09ToGcj02:(CLLocationCoordinate2D)location
{
return [self bd09Decrypt:location.latitude bdLon:location.longitude];
}
+ (CLLocationCoordinate2D)bd09ToWgs84:(CLLocationCoordinate2D)location
{
CLLocationCoordinate2D gcj02 = [self bd09ToGcj02:location];
return [self gcj02Decrypt:gcj02.latitude gjLon:gcj02.longitude];
}
@end
//
// NSString+CalculateSize.h
// Gengmei
//
// Created by Q14 on 2019/8/27.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSString (CalculateSize)
/**
* 计算文字高度,可以处理计算带行间距的
*/
- (CGSize)boundingRectWithSize:(CGSize)size font:(UIFont*)font lineSpacing:(CGFloat)lineSpacing;
/**
* 计算最大行数文字高度,可以处理计算带行间距的
*/
- (CGFloat)boundingRectWithSize:(CGSize)size font:(UIFont*)font lineSpacing:(CGFloat)lineSpacing maxLines:(NSInteger)maxLines;
/**
* 计算是否超过一行 用于给Label 赋值attribute text的时候 超过一行设置lineSpace
*/
- (BOOL)isMoreThanOneLineWithSize:(CGSize)size font:(UIFont *)font lineSpaceing:(CGFloat)lineSpacing;
@end
NS_ASSUME_NONNULL_END
//
// NSString+CalculateSize.m
// Gengmei
//
// Created by Q14 on 2019/8/27.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "NSString+CalculateSize.h"
@implementation NSString (CalculateSize)
/**
* 计算文字高度,可以处理计算带行间距的
*/
- (CGSize)boundingRectWithSize:(CGSize)size font:(UIFont*)font lineSpacing:(CGFloat)lineSpacing {
NSMutableAttributedString *attributeString = [[NSMutableAttributedString alloc] initWithString:self];
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineSpacing = lineSpacing;
[attributeString addAttribute:NSParagraphStyleAttributeName
value:paragraphStyle
range:NSMakeRange(0, self.length)];
[attributeString addAttribute:NSFontAttributeName
value:font
range:NSMakeRange(0, self.length)];
NSStringDrawingOptions options = NSStringDrawingUsesLineFragmentOrigin |
NSStringDrawingUsesFontLeading;
CGRect rect = [attributeString boundingRectWithSize:size
options:options
context:nil];
//文本的高度减去字体高度小于等于行间距,判断为当前只有1行
if ((rect.size.height - font.lineHeight) <= paragraphStyle.lineSpacing) {
if ([self containChinese:self]) { //如果包含中文
rect = CGRectMake(rect.origin.x,
rect.origin.y,
rect.size.width,
rect.size.height-paragraphStyle.lineSpacing);
}
}
return rect.size;
}
//判断如果包含中文
- (BOOL)containChinese:(NSString *)str {
for(int i = 0; i < [str length]; i++) {
int a = [str characterAtIndex:i];
if(a > 0x4e00 && a < 0x9fff) {
return YES;
}
}
return NO;
}
/**
* 计算最大行数文字高度,可以处理计算带行间距的
*/
- (CGFloat)boundingRectWithSize:(CGSize)size font:(UIFont*)font lineSpacing:(CGFloat)lineSpacing maxLines:(NSInteger)maxLines {
if (maxLines <= 0) {
return 0;
}
CGFloat maxHeight = font.lineHeight * maxLines + lineSpacing * (maxLines - 1);
CGSize orginalSize = [self boundingRectWithSize:size
font:font
lineSpacing:lineSpacing];
if (orginalSize.height >= maxHeight) {
return maxHeight;
} else {
return orginalSize.height;
}
}
/**
* 计算是否超过一行 用于给Label 赋值attribute text的时候 超过一行设置lineSpace
*/
- (BOOL)isMoreThanOneLineWithSize:(CGSize)size font:(UIFont *)font lineSpaceing:(CGFloat)lineSpacing {
if ([self boundingRectWithSize:size font:font lineSpacing:lineSpacing].height > font.lineHeight) {
return YES;
} else {
return NO;
}
}
@end
//
// UIView+GMGradient.h
// Gengmei
//
// Created by zhouLiang on 2020/2/23.
// Copyright © 2020 更美互动信息科技有限公司. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface UIView (GMGradient)
@property(nullable, copy) NSArray *colors;
@property(nullable, copy) NSArray<NSNumber *> *locations;
@property CGPoint startPoint;
@property CGPoint endPoint;
+ (UIView *_Nullable)gradientViewWithColors:(NSArray<UIColor *> *_Nullable)colors locations:(NSArray<NSNumber *> *_Nullable)locations startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint;
- (void)setGradientBackgroundWithColors:(NSArray<UIColor *> *_Nullable)colors locations:(NSArray<NSNumber *> *_Nullable)locations startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint;
@end
NS_ASSUME_NONNULL_END
//
// UIView+GMGradient.m
// Gengmei
//
// Created by zhouLiang on 2020/2/23.
// Copyright © 2020 更美互动信息科技有限公司. All rights reserved.
//
#import "UIView+GMGradient.h"
@implementation UIView (GMGradient)
+ (Class)layerClass {
return [CAGradientLayer class];
}
+ (UIView *)gradientViewWithColors:(NSArray<UIColor *> *)colors locations:(NSArray<NSNumber *> *)locations startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint {
UIView *view = [[self alloc] init];
[view setGradientBackgroundWithColors:colors locations:locations startPoint:startPoint endPoint:endPoint];
return view;
}
- (void)setGradientBackgroundWithColors:(NSArray<UIColor *> *)colors locations:(NSArray<NSNumber *> *)locations startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint {
NSMutableArray *colorsM = [NSMutableArray array];
for (UIColor *color in colors) {
[colorsM addObject:(__bridge id)color.CGColor];
}
self.colors = [colorsM copy];
self.locations = locations;
self.startPoint = startPoint;
self.endPoint = endPoint;
}
@end
//UIView+AZGradient.m
//
// UIView+Gradient.h
// Gengmei
//
// Created by Terminator on 2018/8/15.
// Copyright © 2018年 更美互动信息科技有限公司. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UIView (Gradient)
@property(nullable, copy) NSArray *colors;
@property(nullable, copy) NSArray<NSNumber *> *locations;
@property CGPoint startPoint;
@property CGPoint endPoint;
+ (UIView *_Nullable)gradientViewWithColors:(NSArray<UIColor *> *_Nullable)colors locations:(NSArray<NSNumber *> *_Nullable)locations startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint;
- (void)setGradientBackgroundWithColors:(NSArray<UIColor *> *_Nullable)colors locations:(NSArray<NSNumber *> *_Nullable)locations startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint;
/**
从一个颜色变化到另一个颜色
@param beginColor 开始颜色
@param endColor 结束颜色
@param percent 百分比
*/
- (void)getColorWithColor:(UIColor *_Nullable)beginColor endColor:(UIColor *_Nullable)endColor percent:(double)percent;
@end
//
// UIView+Gradient.m
// Gengmei
//
// Created by Terminator on 2018/8/15.
// Copyright © 2018年 更美互动信息科技有限公司. All rights reserved.
//
#import "UIView+Gradient.h"
#import <objc/runtime.h>
@implementation UIView (Gradient)
+ (Class)layerClass {
return [CAGradientLayer class];
}
+ (UIView *)gradientViewWithColors:(NSArray<UIColor *> *)colors locations:(NSArray<NSNumber *> *)locations startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint {
UIView *view = [[self alloc] init];
[view setGradientBackgroundWithColors:colors locations:locations startPoint:startPoint endPoint:endPoint];
return view;
}
- (void)setGradientBackgroundWithColors:(NSArray<UIColor *> *)colors locations:(NSArray<NSNumber *> *)locations startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint {
NSMutableArray *colorsM = [NSMutableArray array];
for (UIColor *color in colors) {
[colorsM addObject:(__bridge id)color.CGColor];
}
self.colors = [colorsM copy];
self.locations = locations;
self.startPoint = startPoint;
self.endPoint = endPoint;
}
- (NSArray *)getRGBDictionaryByColor:(UIColor *)originColor {
CGFloat r = 0,g = 0,b = 0,a = 0;
if ([self respondsToSelector:@selector(getRed:green:blue:alpha:)]) {
[originColor getRed:&r green:&g blue:&b alpha:&a];
} else {
const CGFloat *components = CGColorGetComponents(originColor.CGColor);
r = components[0];
g = components[1];
b = components[2];
a = components[3];
}
return @[@(r),@(g),@(b)];
}
- (NSArray *)transColorBeginColor:(UIColor *)beginColor andEndColor:(UIColor *)endColor {
NSArray<NSNumber *> *beginColorArr = [self getRGBDictionaryByColor:beginColor];
NSArray<NSNumber *> *endColorArr = [self getRGBDictionaryByColor:endColor];
return @[@([endColorArr[0] doubleValue] - [beginColorArr[0] doubleValue]),@([endColorArr[1] doubleValue] - [beginColorArr[1] doubleValue]),@([endColorArr[2] doubleValue] - [beginColorArr[2] doubleValue])];
}
- (void)getColorWithColor:(UIColor *)beginColor endColor:(UIColor *)endColor percent:(double)percent {
NSArray *colorArray = [self transColorBeginColor:beginColor andEndColor:endColor];
NSArray *beginColorArr = [self getRGBDictionaryByColor:beginColor];
double red = [beginColorArr[0] doubleValue] + percent * [colorArray[0] doubleValue];
double green = [beginColorArr[1] doubleValue]+ percent * [colorArray[1] doubleValue];
double blue = [beginColorArr[2] doubleValue] + percent * [colorArray[2] doubleValue];
self.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1];
}
#pragma mark - Getter&Setter
- (NSArray *)colors {
return objc_getAssociatedObject(self, _cmd);
}
- (void)setColors:(NSArray *)colors {
objc_setAssociatedObject(self, @selector(colors), colors, OBJC_ASSOCIATION_COPY_NONATOMIC);
if ([self.layer isKindOfClass:[CAGradientLayer class]]) {
[(CAGradientLayer*)self.layer setColors:self.colors];
}
}
- (NSArray<NSNumber *> *)locations {
return objc_getAssociatedObject(self, _cmd);
}
- (void)setLocations:(NSArray<NSNumber *> *)locations {
objc_setAssociatedObject(self, @selector(locations), locations, OBJC_ASSOCIATION_COPY_NONATOMIC);
if ([self.layer isKindOfClass:[CAGradientLayer class]]) {
[((CAGradientLayer *)self.layer) setLocations:self.locations];
}
}
- (CGPoint)startPoint {
return [objc_getAssociatedObject(self, _cmd) CGPointValue];
}
- (void)setStartPoint:(CGPoint)startPoint {
objc_setAssociatedObject(self, @selector(startPoint), [NSValue valueWithCGPoint:startPoint], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if ([self.layer isKindOfClass:[CAGradientLayer class]]) {
[((CAGradientLayer *)self.layer) setStartPoint:self.startPoint];
}
}
- (CGPoint)endPoint {
return [objc_getAssociatedObject(self, _cmd) CGPointValue];
}
- (void)setEndPoint:(CGPoint)endPoint {
objc_setAssociatedObject(self, @selector(endPoint), [NSValue valueWithCGPoint:endPoint], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if ([self.layer isKindOfClass:[CAGradientLayer class]]) {
[((CAGradientLayer *)self.layer) setEndPoint:self.endPoint];
}
}
@end
//
// WMAlertParameterObject.h
// Gengmei
//
// Created by Sean Lee on 9/8/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import <GMBase/GMObject.h>
/**
* @author licong, 15-11-27 15:11:44
*
* H5执行OC方法时候,传过来的参数
*
*/
@interface WMAlertParameterObject : GMObject
@property (nonatomic, strong) NSString * title;
@property (nonatomic, strong) NSString * content;
@property (nonatomic, strong) NSString * confirmText;
@property (nonatomic, strong) NSString * cancelText;
@property (nonatomic, strong) NSString * confirmCallback;
@property (nonatomic, strong) NSString * cancelCallback;
@end
@interface WMToastParameterObject : GMObject
@property (nonatomic, strong) NSString * text;
@property (nonatomic, assign) NSInteger duration;
@end
\ No newline at end of file
//
// WMAlertParameterObject.m
// Gengmei
//
// Created by Sean Lee on 9/8/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "WMAlertParameterObject.h"
@implementation WMAlertParameterObject
@end
@implementation WMToastParameterObject
@end
\ No newline at end of file
//
// WMTagObject.h
// Gengmei
//
// Created by Sean Lee on 1/21/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import <GMBase/GMObject.h>
@class GMZoneTagsObject;
@protocol WMTagObject @end
/**
* @brief 创建日记本时使用
*/
@interface WMTagObject : GMObject
/** @brief tag名称 name*/
@property (nonatomic, strong) NSString *name;
/** @brief tagId概念一样,服务器返回来的是int */
@property (nonatomic, assign) NSInteger tagId;
/*** @brief 是否选中 在3级选择项目页使用*/
@property (nonatomic, assign) BOOL isSelected;
@property (nonatomic, assign) BOOL freeToAdd;
@property (nonatomic, assign) BOOL isShowImg;
/** 活动标签tag, 7770 */
@property (nonatomic, assign) BOOL isActivityTags;
#pragma mark - since 7110
/** 热度分 */
@property (nonatomic, copy) NSString *hotScore;
/** 标签样式 */
@property (nonatomic, copy) NSString *style;
/** 标签对应图片 */
@property (nonatomic, copy) NSString *styleImageUrl;
/** 高亮 */
@property (nonatomic, copy) NSString *highlight;
/** 标签点击来源 */
@property (nonatomic, copy) NSString *tagSource;
@property (nonatomic,copy) NSString *isNewTag;
+ (WMTagObject *)constructionModel:(GMZoneTagsObject *)zoneObject;
@end
//
// WMTagObject.m
// Gengmei
//
// Created by Sean Lee on 1/21/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "WMTagObject.h"
#import "GMZoneTagsObject.h"
@implementation WMTagObject
+ (WMTagObject *)constructionModel:(GMZoneTagsObject *)zoneObject {
WMTagObject *tagObject = [[WMTagObject alloc] init];
tagObject.tagId = zoneObject.id;
tagObject.name = zoneObject.name;
tagObject.style = zoneObject.tagType;
return tagObject;
}
@end
//
// GMSegmentViewWithRedBadge.h
// Gengmei
//
// Created by 汪俊 on 2016/11/15.
// Copyright © 2016年 更美互动信息科技有限公司. All rights reserved.
//
@import GMKit;
/**
带小红点的segmentView
*/
@interface GMSegmentViewWithRedBadge : WYSegmentView
- (void)showBadge:(BOOL)isShow atIndex:(NSInteger)index NS_SWIFT_NAME(show(badge:at:));
- (BOOL)isShowBadgeAtIndex:(NSInteger)index;
@end
//
// GMSegmentViewWithRedBadge.m
// Gengmei
//
// Created by 汪俊 on 2016/11/15.
// Copyright © 2016年 更美互动信息科技有限公司. All rights reserved.
//
#import "GMSegmentViewWithRedBadge.h"
@implementation GMSegmentViewWithRedBadge {
NSArray<UIImageView *> *_badgeViews;
}
- (void)setup {
[super setup];
self.frame = CGRectMake(0, 0, MAINSCREEN_WIDTH, 40);
self.backgroundColor = [UIColor whiteColor];
self.titleTextAttributes = @{NSFontAttributeName:[UIFont gmFont:15],
NSForegroundColorAttributeName:UIColor.auxiliaryTextDark};
self.selectedTitleTextAttributes = @{NSFontAttributeName:[UIFont gmBoldFont:15],
NSForegroundColorAttributeName:UIColor.headlineText};
self.showIndicator = YES;
self.widthStyle = WYSegmentViewWidthStyleEqual;
self.indicatorColor = UIColor.auxiliaryTextGreen;
}
- (void)layoutSegments {
[super layoutSegments];
NSMutableArray *badges = [NSMutableArray array];
//添加小红点
for (UIView *subview in self.scrollView.subviews) {
for (UIView *view in subview.subviews) {
if ([view isKindOfClass:[UIButton class]]) {
UIImageView *redBadge = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"red_badge"]];
redBadge.hidden = YES;
UIButton *button = (UIButton *)view;
[button.titleLabel addSubview:redBadge];
[redBadge mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(-1);
make.left.mas_equalTo(button.titleLabel.width);
}];
[badges addObject:redBadge];
}
}
}
_badgeViews = badges;
}
- (void)showBadge:(BOOL)isShow atIndex: (NSInteger)index {
UIImageView *badgeView = _badgeViews[index];
badgeView.hidden = !isShow;
}
- (BOOL)isShowBadgeAtIndex:(NSInteger)index {
return !_badgeViews[index].hidden;
}
@end
//
// WYSegmentView+Factory.h
// Gengmei
//
// Created by wangyang on 16/2/17.
// Copyright © 2016年 Wanmeichuangyi. All rights reserved.
//
#import <GMKit/WYSegmentView.h>
NS_ASSUME_NONNULL_BEGIN
@interface WYSegmentView (Factory)
/**
提供一个基础的,字体大小、颜色符合UI规范的segment。
*/
+ (WYSegmentView *)baseThemeSegment;
/**
* @author wangyang, 16-02-18 15:02:23
*
* @brief 用于多个标题,segment可以滑动的设计
* @since 5.9.1
*/
+ (WYSegmentView *)dynamicSegment;
/**
* @author wangyang, 16-02-18 15:02:49
*
* @brief 用于标题个数固定,segment宽度一样的设计
* @since 5.9.1
*/
+ (WYSegmentView *)fullWidthSegment;
/**
* @author wangyang, 16-02-18 16:02:51
*
* @brief 用于我的订单页的segment
* @since 5.9.1
*/
+ (WYSegmentView *)myOrderListSegment;
@end
NS_ASSUME_NONNULL_END
//
// WYSegmentView+Factory.m
// Gengmei
//
// Created by wangyang on 16/2/17.
// Copyright © 2016年 Wanmeichuangyi. All rights reserved.
//
#import "WYSegmentView+Factory.h"
@import GMKit;
@implementation WYSegmentView (Factory)
+ (WYSegmentView *)baseThemeSegment {
WYSegmentView *segment = [[WYSegmentView alloc] initWithFrame:CGRectMake(0, 0, MAINSCREEN_WIDTH, 40)];
segment.backgroundColor = [UIColor whiteColor];
segment.showIndicator = YES;
return segment;
}
+ (WYSegmentView *)dynamicSegment {
WYSegmentView *segment = [self baseThemeSegment];
segment.titleTextAttributes = @{NSFontAttributeName:[UIFont gmFont:15],
NSForegroundColorAttributeName:UIColor.auxiliaryTextDark};
segment.selectedTitleTextAttributes = @{NSFontAttributeName:[UIFont gmBoldFont:15],
NSForegroundColorAttributeName:UIColor.headlineText};
segment.scrollView.contentInset = UIEdgeInsetsMake(0, 6, 0, 6);
segment.widthStyle = WYSegmentViewWidthStyleDynamic;
segment.itemHorizonalPadding = 14;
return segment;
}
+ (WYSegmentView *)fullWidthSegment {
WYSegmentView *segment = [self baseThemeSegment];
segment.titleTextAttributes = @{NSFontAttributeName:[UIFont gmFont:15],
NSForegroundColorAttributeName:UIColor.auxiliaryTextDark};
segment.selectedTitleTextAttributes = @{NSFontAttributeName:[UIFont gmBoldFont:15],
NSForegroundColorAttributeName:UIColor.headlineText};
segment.widthStyle = WYSegmentViewWidthStyleEqual;
return segment;
}
+ (WYSegmentView *)myOrderListSegment {
WYSegmentView *segment = [self baseThemeSegment];
segment.titleTextAttributes = @{NSFontAttributeName:[UIFont gmFont:15],
NSForegroundColorAttributeName:UIColor.auxiliaryTextDark};
segment.selectedTitleTextAttributes = @{NSFontAttributeName:[UIFont gmBoldFont:15],
NSForegroundColorAttributeName:UIColor.headlineText};
segment.widthStyle = WYSegmentViewWidthStyleEqual;
return segment;
}
@end
//
// GMInstallmentView.h
// Gengmei
//
// Created by Terminator on 16/10/23.
// Copyright © 2016年 更美互动信息科技有限公司. All rights reserved.
//
#import "GMView.h"
#import "GMImageView.h"
#import "GMHighlightLabel.h"
@interface GMInstallmentView : GMView
@property (nonatomic, strong) GMHighlightLabel *promoteLabel;
@property (nonatomic, strong) GMImageView *icon;
@end
//
// GMInstallmentView.m
// Gengmei
//
// Created by Terminator on 16/10/23.
// Copyright © 2016年 更美互动信息科技有限公司. All rights reserved.
//
#import "GMInstallmentView.h"
#import <GMKit/GMFont.h>
#import <GMKit/UIColor+GMTheme.h>
#import <GMKit/UIView+LineWithAutolayout.h>
@interface GMInstallmentView()
@property (nonatomic, strong) GMView *installView;
@end
@implementation GMInstallmentView
- (void)setup {
[super setup];
_installView = [[GMView alloc] init];
[_installView addTopLineWithLeft:15 right:-15];
[self addSubview:_installView];
[_installView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(0);
make.right.mas_equalTo(0);
make.top.mas_equalTo(0);
make.height.mas_equalTo(45);
}];
_icon = [[GMImageView alloc] init];
_icon.image = [UIImage imageNamed:@"installment_icon"];
[_installView addSubview:_icon];
[_icon mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(15);
make.centerY.mas_equalTo(0);
}];
_promoteLabel = [[GMHighlightLabel alloc] init];
[_promoteLabel setContentHuggingPriority:100 forAxis:UILayoutConstraintAxisHorizontal];
[_promoteLabel setContentCompressionResistancePriority:1000 forAxis:UILayoutConstraintAxisHorizontal];
_promoteLabel.hightlightColor = UIColor.secondaryVisual;
_promoteLabel.normalColor = UIColor.bodyText ;
_promoteLabel.textColor = UIColor.bodyText;
_promoteLabel.font = [UIFont gmFont:11];
_promoteLabel.hightlightFont = [UIFont gmBoldFont:11];
[_installView addSubview:_promoteLabel];
[_promoteLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(_icon.mas_right).offset(10);
make.right.mas_lessThanOrEqualTo(-15);
make.centerY.equalTo(_icon.mas_centerY);
}];
}
@end
//
// GMInsuranceView.h
// Gengmei
//
// Created by 翟国钧 on 2017/2/13.
// Copyright © 2017年 更美互动信息科技有限公司. All rights reserved.
//
#import "GMView.h"
#import "GMLabel.h"
@interface GMInsuranceView : GMView
@property (nonatomic, strong) GMLabel *insuranceLabel;
@end
//
// GMInsuranceView.m
// Gengmei
//
// Created by 翟国钧 on 2017/2/13.
// Copyright © 2017年 更美互动信息科技有限公司. All rights reserved.
//
#import "GMInsuranceView.h"
#import "GMImageView.h"
#import <GMKit/UIColor+GMTheme.h>
#import <GMKit/UIView+LineWithAutolayout.h>
@interface GMInsuranceView()
@property (nonatomic, strong) GMView *installView;
@property (nonatomic, strong) GMImageView *icon;
@end
@implementation GMInsuranceView
- (void)setup {
[super setup];
_installView = [[GMView alloc] init];
[_installView addTopLineWithLeft:15 right:-15];
[self addSubview:_installView];
[_installView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(0);
make.right.mas_equalTo(0);
make.top.mas_equalTo(0);
make.height.mas_equalTo(45);
}];
_icon = [[GMImageView alloc] init];
_icon.image = [UIImage imageNamed:@"welfare_insurance"];
[_installView addSubview:_icon];
[_icon mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(15);
make.centerY.mas_equalTo(0);
}];
_insuranceLabel = [GMLabel labelWithTextColor:UIColor.bodyText fontSize:11];
[_installView addSubview:_insuranceLabel];
[_insuranceLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(_icon.mas_right).offset(10);
make.centerY.equalTo(_icon.mas_centerY);
}];
}
@end
//
// GMLiveWelfareView.h
// Gengmei
//
// Created by 汪俊 on 2017/3/30.
// Copyright © 2017年 更美互动信息科技有限公司. All rights reserved.
//
#import "GMImageView.h"
#import "GMInstallmentView.h"
#import "GMInsuranceView.h"
//#import "Gengmei-Swift.h"
@class AllLayoutButton;
@class GMLiveWelfareView;
@class GMAdLabel;
@protocol GMLiveWelfareViewDelegate <NSObject>
- (void)didSelectedWelfareView:(GMLiveWelfareView *)welFareView;
- (void)didClickedHospitalDetailButton:(AllLayoutButton *)button;
@end
/**
直播和回放页面的美购
*/
@interface GMLiveWelfareView : GMView
@property (nonatomic , strong) GMView *welfareContainer;
/** @brief 美购大图片 */
@property (nonatomic , strong) GMImageView *bigIconImage;
/** @brief 预付价 */
@property (nonatomic , strong) GMImageView *yufuImage;
/** @brief 已售完 */
@property (nonatomic , strong) GMImageView *sallOutImage;
/** @brief 美购标题 */
@property (nonatomic , strong) GMLabel *welfareTitle;
/** @brief 美购简介 */
@property (nonatomic , strong) GMAdLabel *welfareContent;
/** @brief 更美价 */
@property (nonatomic , strong) GMLabel *gengmeiPrice;
/** @brief 市场价 */
@property (nonatomic , strong) GMLabel *marketPrice;
/** @brief 删除线 */
@property (nonatomic , strong) GMLabel *deleteLabel;
/** @brief 人民币(元/元起) */
@property (nonatomic , strong) GMLabel *yuanLabel;
/** @brief 预付款标签 */
@property (nonatomic , strong) GMLabel *yufuLabel;
/** @brief 申请数 */
@property (nonatomic , strong) GMLabel *applyNumber;
/** @brief 剩余多少个*/
@property (nonatomic , strong) GMLabel *surplusLabel;
/** @brief 城市 */
@property (nonatomic , strong) GMLabel *cityLabel;
/** @brief 医生 */
@property (nonatomic , strong) GMLabel *doctorLabel;
/** @brief 机构 */
@property (nonatomic , strong) GMLabel *hospitalLabel;
/**状态栏背景*/
@property (nonatomic, strong) GMView *statusBG;
/** @brief */
@property (nonatomic , strong) UIView *surplusBG;
/** @brief 秒杀价图片*/
@property (nonatomic , strong) GMImageView *seckillIcon;
/** seckill是否隐藏*/
@property (nonatomic, assign) BOOL isSeckillHidden;
/**
展示推广label
@author wangjun 16-08-01 in 6.3.0
*/
@property (nonatomic, assign) BOOL showPromotion;
/**
分期提示
*/
//@property (nonatomic, strong) GMInstallmentView *installmentView;
/**
保险提示
*/
//@property (nonatomic, strong) GMInsuranceView *insuranceView;
/**
是否展示推分期
*/
//@property (nonatomic, assign) BOOL showInstallment;
/**
是否展示保险
*/
//@property (nonatomic, assign) BOOL showInsurance;
@property (nonatomic , strong) UIImageView *hospitalBg;
@property (nonatomic, weak) id<GMLiveWelfareViewDelegate> delegate;
- (void)show;
- (void)hide;
@end
//
// GMLiveWelfareView.m
// Gengmei
//
// Created by 汪俊 on 2017/3/30.
// Copyright © 2017年 更美互动信息科技有限公司. All rights reserved.
//
#import "GMLiveWelfareView.h"
#import "UIView+Layout.h"
#import <GMBase/GMBase-Swift.h>
//#import "GMAdLabel.h"
#import <GMKit/GMKit-umbrella.h>
//#import "AllLayoutButton.swift"
@import GMBaseSwift;
@interface GMLiveWelfareView ()
@property (nonatomic , strong) AllLayoutButton *rightButton;
@property (nonatomic , strong) GMLabel *leftLabel;
@end
@implementation GMLiveWelfareView
- (void)setup {
[super setup];
__weak typeof(self)weakSelf = self;
[self setTapActionWithBlock:^{
[weakSelf welfareItemDidClicked];
}];
self.backgroundColor = UIColor.whiteColor;
_leftLabel = [GMLabel labelWithTextColor:UIColor.headlineText fontSize:16];
_leftLabel.text = @"相关美购";
[self addSubview:_leftLabel];
[_leftLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(15);
make.top.mas_equalTo(14);
}];
_hospitalBg = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"live_hospital_bg"]];
_hospitalBg.userInteractionEnabled = YES;
[self addSubview:_hospitalBg];
[_hospitalBg mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(0);
make.top.mas_equalTo(4.5);
make.size.mas_equalTo(CGSizeMake(102, 33));
}];
_rightButton = [[AllLayoutButton alloc] initWithImage:[UIImage imageNamed:@"live_hospital_arrow"] title:@"医生主页" titleColor:UIColor.whiteColor font:[UIFont gmFont:15]];
_rightButton.space = 6;
_rightButton.type = ImageTitleTypeImageRightTitleLeft;
_rightButton.enableAdaptive = YES;
_rightButton.margin = UIEdgeInsetsMake(0, 18, 0, 0);
[_rightButton addTarget:self action:@selector(hospitalDetailAction:) forControlEvents:UIControlEventTouchUpInside];
[_hospitalBg addSubview:_rightButton];
[_rightButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(0);
make.centerY.mas_equalTo(_hospitalBg);
}];
[self addHorizontalLineWithTop:42 left:0 right:0];
_welfareContainer = [[GMView alloc] init];
// _welfareContainer.backgroundColor = [UIColor redColor];
[self addSubview:_welfareContainer];
[_welfareContainer mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(42);
make.left.right.mas_equalTo(0);
if (UIDevice.aspectRatioType == GMDeviceAspectRatioTypeWide){
make.bottom.mas_equalTo(-18);
} else{
make.bottom.mas_equalTo(0);
}
}];
_bigIconImage = [[GMImageView alloc] init];
_bigIconImage.layer.cornerRadius = 2.5;
_bigIconImage.layer.masksToBounds = YES;
_bigIconImage.backgroundColor = UIColor.background;
[_welfareContainer addSubview:_bigIconImage];
_surplusBG = [[UIView alloc] init];
_surplusBG.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.7];
[_bigIconImage addSubview:_surplusBG];
_statusBG = [[GMView alloc] init];
[_bigIconImage addSubview:_statusBG];
_surplusLabel = [GMLabel labelWithTextColor:UIColor.whiteColor fontSize:10];
_surplusLabel.textColor = [UIColor whiteColor];
_surplusLabel.font = [UIFont gmFont:10];
[_statusBG addSubview:_surplusLabel];
_welfareContent = [[GMAdLabel alloc] init];//[GMAdLabel labelWithTextColor:UIColor.headlineText fontSize:14];
_welfareContent.textColor = UIColor.headlineText;
_welfareContent.font = [UIFont gmFont:14];
_welfareContent.numberOfLines = 2;
_welfareContent.adLabelTop = 4;
[_welfareContainer addSubview:_welfareContent];
_doctorLabel =[[GMLabel alloc] init];
_doctorLabel.textAlignment = NSTextAlignmentLeft;
_doctorLabel.verticalAlignment = GMLabelVerticalAlignmentMiddle;
_doctorLabel.font = [UIFont gmFont:12];
_doctorLabel.textColor = UIColor.auxiliaryTextLight;
[_welfareContainer addSubview:_doctorLabel];
_hospitalLabel =[[GMLabel alloc] init];
_hospitalLabel.textAlignment = NSTextAlignmentLeft;
_hospitalLabel.verticalAlignment = GMLabelVerticalAlignmentMiddle;
_hospitalLabel.font = [UIFont gmFont:12];
_hospitalLabel.textColor = UIColor.auxiliaryTextLight;
[_welfareContainer addSubview:_hospitalLabel];
[_doctorLabel setContentHuggingPriority:252 forAxis:UILayoutConstraintAxisHorizontal];
[_doctorLabel setContentCompressionResistancePriority:751 forAxis:UILayoutConstraintAxisHorizontal];
[_hospitalLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisHorizontal];
[_hospitalLabel setContentCompressionResistancePriority:750 forAxis:UILayoutConstraintAxisHorizontal];
_seckillIcon = [[GMImageView alloc]initWithImage:[UIImage imageNamed:@"seckill_shop"]];
[_welfareContainer addSubview:_seckillIcon];
_gengmeiPrice = [GMLabel labelWithTextColor:UIColor.secondaryVisual fontSize:15];
[_welfareContainer addSubview:_gengmeiPrice];
_gengmeiPrice.font = [UIFont gmFont:15];
_yuanLabel = [GMLabel labelWithTextColor:UIColor.secondaryVisual fontSize:11];
[_welfareContainer addSubview:_yuanLabel];
_marketPrice = [GMLabel labelWithTextColor:UIColor.auxiliaryTextLight fontSize:9];
[_welfareContainer addSubview:_marketPrice];
_deleteLabel = [GMLabel new];
_deleteLabel.backgroundColor = UIColor.auxiliaryTextLight;
[_marketPrice addSubview:_deleteLabel];
_applyNumber = [GMLabel labelWithTextColor:UIColor.auxiliaryTextLight fontSize:11];
[_welfareContainer addSubview:_applyNumber];
//
// _installmentView = [[GMInstallmentView alloc] init];
// _installmentView.layer.masksToBounds = YES;
// [_welfareContainer addSubview:_installmentView];
//
// _insuranceView = [[GMInsuranceView alloc] init];
// _insuranceView.layer.masksToBounds = YES;
// [_welfareContainer addSubview:_insuranceView];
}
- (void)updateConstraints {
[_bigIconImage mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(15);
make.top.mas_equalTo(15);
make.size.mas_equalTo(CGSizeMake(100, 100));
}];
[_statusBG mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(0);
make.bottom.equalTo(_bigIconImage.mas_bottom).offset(-8);
}];
[_surplusLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(5);
make.right.mas_equalTo(-5);
make.top.mas_equalTo(0);
make.bottom.mas_equalTo(0);
}];
[_doctorLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(_bigIconImage.mas_right).offset(15);
make.top.equalTo(_welfareContent.mas_bottom).offset(5);
}];
[_hospitalLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(_doctorLabel.mas_right).offset(10);
make.right.mas_equalTo(-10);
make.top.equalTo(_doctorLabel.mas_top);
}];
[_seckillIcon mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(_bigIconImage.mas_right).offset(15);
make.centerY.equalTo(_gengmeiPrice.mas_centerY);
make.size.sizeOffset(CGSizeMake(25, 13));
}];
[_deleteLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(0);
make.height.mas_equalTo(0.5);
make.centerY.equalTo(_marketPrice.mas_centerY).offset(0);
make.width.mas_equalTo(_marketPrice.mas_width);
}];
[_applyNumber mas_remakeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(-10);
make.centerY.equalTo(_gengmeiPrice.mas_centerY);
}];
[_yuanLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(_gengmeiPrice.mas_right).offset(2);
make.bottom.equalTo(_bigIconImage.mas_bottom).offset(0);
}];
// [_installmentView mas_remakeConstraints:^(MASConstraintMaker *make) {
// make.top.equalTo(_bigIconImage.mas_bottom).offset(15);
// make.left.right.mas_equalTo(0);
// make.height.mas_equalTo(35);
// }];
// [_insuranceView mas_remakeConstraints:^(MASConstraintMaker *make) {
// make.top.equalTo(_installmentView.mas_bottom).offset(0);
// make.left.right.mas_equalTo(0);
// make.height.mas_equalTo(35);
// }];
[_marketPrice mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(_yuanLabel.hidden?_gengmeiPrice.mas_right:_yuanLabel.mas_right).offset(5);
make.bottom.equalTo(_bigIconImage.mas_bottom).offset(0);
}];
[_gengmeiPrice mas_remakeConstraints:^(MASConstraintMaker *make) {
if (_isSeckillHidden) {
make.left.equalTo(_bigIconImage.mas_right).offset(15);
}else{
make.left.equalTo(_seckillIcon.mas_right).offset(5);
}
make.bottom.mas_equalTo(_bigIconImage.mas_bottom).offset(2);
}];
[_welfareContent mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(10);
if (_showPromotion) {
make.left.equalTo(_bigIconImage.mas_right).offset(20);
} else {
make.left.equalTo(_bigIconImage.mas_right).offset(15);
}
make.right.mas_equalTo(-15);
}];
// [_installmentView mas_remakeConstraints:^(MASConstraintMaker *make) {
// make.top.equalTo(_bigIconImage.mas_bottom).offset(15);
// make.left.right.mas_equalTo(0);
// if (_showInstallment) {
// make.height.mas_equalTo(35);
// } else {
// make.height.mas_equalTo(0);
// }
// }];
//
// [_insuranceView mas_remakeConstraints:^(MASConstraintMaker *make) {
// make.top.equalTo(_installmentView.mas_bottom).offset(0);
// make.left.right.mas_equalTo(0);
// if (_showInsurance) {
// make.height.mas_equalTo(35);
// } else {
// make.height.mas_equalTo(0);
// }
// }];
[super updateConstraints];
}
- (void)show {
self.hidden = NO;
[self mas_updateConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo( MAINSCREEN_HEIGHT - self.height);
}];
[UIView animateWithDuration:0.25 animations:^{
[self.superview layoutIfNeeded];
}];
}
- (void)hide {
[self mas_updateConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(MAINSCREEN_HEIGHT);
}];
[UIView animateWithDuration:0.25 animations:^{
[self.superview layoutIfNeeded];
} completion:^(BOOL finished) {
self.hidden = YES;
}];
}
- (void)welfareItemDidClicked {
if ([self.delegate respondsToSelector:@selector(didSelectedWelfareView:)]) {
[self.delegate didSelectedWelfareView:self];
}
}
- (void)hospitalDetailAction:(AllLayoutButton *)button {
if ([self.delegate respondsToSelector:@selector(didClickedHospitalDetailButton:)]) {
[self.delegate didClickedHospitalDetailButton:button];
}
}
@end
//
// GMWebViewModel.h
// Gengmei
//
// Created by MoMo on 2019/12/12.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "WMFetchDataViewModel.h"
/** webView 相关数据 */
NS_ASSUME_NONNULL_BEGIN
@interface GMWebViewModel : WMFetchDataViewModel
/// 问题页,置顶回答id
@property (nonatomic, copy) NSString *top_answer_id;
/** 回复问题id */
@property (nonatomic, copy) NSString *questionId;
/** 视频Token */
@property (nonatomic, copy) NSString *videoToken;
@end
NS_ASSUME_NONNULL_END
//
// GMWebViewModel.m
// Gengmei
//
// Created by MoMo on 2019/12/12.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMWebViewModel.h"
@implementation GMWebViewModel
@end
//
// UIViewController+UrlScheme.h
// Gengmei
//
// Created by Thierry on 4/21/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface UIViewController (UrlScheme)
/**
区域埋点字段
*/
@property (nonatomic, strong) NSDictionary *referDict;
@end
NS_ASSUME_NONNULL_END
//
// UIViewController+UrlScheme.m
// Gengmei
//
// Created by Thierry on 4/21/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import <objc/runtime.h>
#import "UIViewController+UrlScheme.h"
@implementation UIViewController (UrlScheme)
#pragma mark -------------------
- (void)setReferDict:(NSDictionary *)referDict {
objc_setAssociatedObject(self, @selector(referDict), referDict, OBJC_ASSOCIATION_RETAIN);
}
- (NSDictionary *)referDict {
return objc_getAssociatedObject(self, @selector(referDict));
}
@end
//
// WMBaseViewController+FadeNavigation.h
// Gengmei
//
// Created by wangyang on 2017/6/6.
// Copyright © 2017年 更美互动信息科技有限公司. All rights reserved.
//
#import "WMBaseViewController.h"
@interface WMBaseViewController (FadeNavigation)
@property(nonatomic, assign) CGFloat animationDistance;
// 考虑到如果scrollview y偏移量为负数, 添加次当做scrollView最初的Y偏移量
@property (nonatomic, assign) CGFloat contentOffsetY;
// 返回当前的alpha值
- (CGFloat)animationBar:(OCNavigationBar *)bar withScrollView:(UIScrollView *)scrollView;
@end
//
// WMBaseViewController+FadeNavigation.m
// Gengmei
//
// Created by wangyang on 2017/6/6.
// Copyright © 2017年 更美互动信息科技有限公司. All rights reserved.
//
#import "WMBaseViewController+FadeNavigation.h"
#import <objc/runtime.h>
@implementation WMBaseViewController (FadeNavigation)
- (void)setAnimationDistance:(CGFloat)animationDistance {
objc_setAssociatedObject(self, @selector(animationDistance), @(animationDistance), OBJC_ASSOCIATION_RETAIN);
}
- (CGFloat)animationDistance {
NSNumber *_animationDistance = objc_getAssociatedObject(self, _cmd);
if (_animationDistance == nil) {
return 0;
} else {
return [_animationDistance doubleValue];
}
}
- (void)setContentOffsetY:(CGFloat)contentOffsetY {
objc_setAssociatedObject(self, @selector(contentOffsetY), @(contentOffsetY), OBJC_ASSOCIATION_RETAIN);
}
- (CGFloat)contentOffsetY {
NSNumber *_contentOffsetY = objc_getAssociatedObject(self, _cmd);
if (_contentOffsetY == nil) {
return 0;
}
return _contentOffsetY.doubleValue;
}
- (CGFloat)animationBar:(OCNavigationBar *)bar withScrollView:(UIScrollView *)scrollView {
CGFloat offsetY = scrollView.contentOffset.y;
if (self.contentOffsetY > 0) {
offsetY = self.contentOffsetY;
}
CGFloat alpha = MIN(1.0, offsetY / self.animationDistance);
if (offsetY > self.animationDistance) {
// 导航栏不透明
bar.backgroundColor = [UIColor colorWithWhite:1 alpha:alpha];
bar.titleLabel.textColor = [UIColor colorWithWhite:0 alpha:alpha];
bar.isShowShadow = YES;
} else {
// 导航栏渐变到透明
bar.backgroundColor = [UIColor colorWithWhite:1 alpha:alpha];
bar.titleLabel.textColor = [UIColor colorWithWhite:0 alpha:alpha];
bar.isShowShadow = NO;
}
self.navigationBar.titleLabel.hidden = alpha == 0;
return alpha;
}
@end
//
// WMBaseViewController+OCNavigationBar.h
// Gengmei
//
// Created by wangyang on 16/7/8.
// Copyright © 2016年 更美互动信息科技有限公司. All rights reserved.
//
#import "WMBaseViewController.h"
/*** @brief 右按钮的 按钮类型 */
typedef NS_ENUM(NSUInteger, OCBarButtonImageType){
/*** @brief 默认没有右按钮*/
OCBarButtonImageTypeDefault = 0,
/*** @brief 图片分享按钮 */
OCBarButtonImageTypeShare = 1,
/*** @brief 写新日记 */
OCBarButtonImageTypeDiary,
/*** @brief 搜索按钮。如果是继承于WMBaseViewController,这个option的action不需要单独实现,在base中已经实现了 */
OCBarButtonImageTypeSearch,
/*** @brief 分享返现,具体方法没有实现,请在子类中实现*/
OCBarButtonImageTypeShareCashback,
/*** @brief 客服,请在子类中实现*/
OCBarButtonImageTypeCustomerServices,
};
@interface WMBaseViewController (OCNavigationBar)
/**
* @brief 设置导航栏 左边默认是返回按钮
*
* @param title 导航tilte
* @param rightTitle 右边文字型的按钮
*/
- (void)setNavigateTitle:(NSString *)title rightButtonTitle:(NSString *)rightButtonTitle;
/**
* @brief 设置导航栏 左边默认是返回按钮
*
* @param title 导航tilte
* @param rightButton 右边是图片型的按钮
*/
- (void)setNavigateTitle:(NSString *)title rightButtonOption:(OCBarButtonImageType)rightButtonOption;
/**
* @brief 当右侧是标题按钮时,设置右侧标题按钮不同状态下的色值
* @param normalColor 常态下色值
* @param highlightColor 高亮下色值
* @param disableColor 失效下色值
*/
- (void)setRightTitleNormalColor:(UIColor *)normalColor highlightColor:(UIColor *)highlightColor disableColor:(UIColor *)disableColor;
/**
* @author wangyang
*
* 通过此方法可以返回一个rightButton图片名,用于navigationBar.rightIcon
*/
- (NSString *)navigationBarIconFromType:(OCBarButtonImageType)type;
@end
//
// WMBaseViewController+OCNavigationBar.m
// Gengmei
//
// Created by wangyang on 16/7/8.
// Copyright © 2016年 更美互动信息科技有限公司. All rights reserved.
//
#import "WMBaseViewController.h"
#import "WMBaseViewController+OCNavigationBar.h"
@implementation WMBaseViewController (OCNavigationBar)
- (void)setNavigateTitle:(NSString *)title rightButtonTitle:(NSString *)rightButtonTitle{
self.navigationBar.title = title;
self.navigationBar.rightTitle = rightButtonTitle;
}
- (void)setNavigateTitle:(NSString *)title rightButtonOption:(OCBarButtonImageType)rightButtonOption{
self.navigationBar.title = title;
self.navigationBar.rightIcon = [self navigationBarIconFromType:rightButtonOption];
}
- (NSString *)navigationBarIconFromType:(OCBarButtonImageType)type {
switch (type) {
case OCBarButtonImageTypeShare:
return @"share";
case OCBarButtonImageTypeDiary:
return @"write";
case OCBarButtonImageTypeSearch:
return @"search_green";
case OCBarButtonImageTypeShareCashback:
return @"share_cashback";
case OCBarButtonImageTypeCustomerServices:
return @"customer_service";
default:
return @"";
}
}
- (void)setRightTitleNormalColor:(UIColor *)normalColor highlightColor:(UIColor *)highlightColor disableColor:(UIColor *)disableColor{
if (normalColor) {
[self.navigationBar.rightButton setTitleColor:normalColor forState:UIControlStateNormal];
}
if (highlightColor) {
[self.navigationBar.rightButton setTitleColor:normalColor forState:UIControlStateHighlighted];
}
if (disableColor) {
[self.navigationBar.rightButton setTitleColor:normalColor forState:UIControlStateDisabled];
}
}
@end
...@@ -9,9 +9,8 @@ ...@@ -9,9 +9,8 @@
#import "GMNavigationController.h" #import "GMNavigationController.h"
#import "UIViewController+PushType.h" #import "UIViewController+PushType.h"
#import "GMPresentAnimation.h" #import "GMPresentAnimation.h"
@import GMKit;
#import "GMCustomNavigationAnimationProtocol.h" #import "GMCustomNavigationAnimationProtocol.h"
@import GMKit;
@interface GMNavigationController ()<UINavigationControllerDelegate> @interface GMNavigationController ()<UINavigationControllerDelegate>
...@@ -22,7 +21,7 @@ ...@@ -22,7 +21,7 @@
- (void)viewDidLoad { - (void)viewDidLoad {
[super viewDidLoad]; [super viewDidLoad];
self.delegate = self; self.delegate = self;
self.fd_viewControllerBasedNavigationBarAppearanceEnabled = NO; // self.fd_viewControllerBasedNavigationBarAppearanceEnabled = NO;
} }
- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController - (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
......
//
// JXCategoryBaseCell.h
// UI系列测试
//
// Created by jiaxin on 2018/3/15.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "JXCategoryBaseCellModel.h"
@interface JXCategoryBaseCell : UICollectionViewCell
@property (nonatomic, strong) JXCategoryBaseCellModel *cellModel;
- (void)initializeViews NS_REQUIRES_SUPER;
- (void)reloadData:(JXCategoryBaseCellModel *)cellModel NS_REQUIRES_SUPER;
@end
//
// JXCategoryBaseCell.m
// UI系列测试
//
// Created by jiaxin on 2018/3/15.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryBaseCell.h"
@interface JXCategoryBaseCell ()
@end
@implementation JXCategoryBaseCell
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self initializeViews];
}
return self;
}
- (void)initializeViews
{
}
- (void)reloadData:(JXCategoryBaseCellModel *)cellModel {
self.cellModel = cellModel;
}
@end
//
// JXCategoryBaseCellModel.h
// UI系列测试
//
// Created by jiaxin on 2018/3/15.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface JXCategoryBaseCellModel : NSObject
@property (nonatomic, assign) NSUInteger index;
@property (nonatomic, assign) BOOL selected;
@property (nonatomic, assign) CGFloat cellWidth;
@property (nonatomic, assign) CGFloat cellSpacing;
@property (nonatomic, assign) BOOL cellWidthZoomEnabled;
@property (nonatomic, assign) CGFloat cellWidthZoomScale;
@end
//
// JXCategoryBaseCellModel.m
// UI系列测试
//
// Created by jiaxin on 2018/3/15.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryBaseCellModel.h"
@implementation JXCategoryBaseCellModel
- (CGFloat)cellWidth {
if (_cellWidthZoomEnabled) {
return _cellWidth * _cellWidthZoomScale;
}
return _cellWidth;
}
@end
//
// JXCategoryView.h
// UI系列测试
//
// Created by jiaxin on 2018/3/15.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "JXCategoryBaseCell.h"
#import "JXCategoryBaseCellModel.h"
#import "JXCategoryCollectionView.h"
#import "JXCategoryViewDefines.h"
@class JXCategoryBaseView;
@protocol JXCategoryViewDelegate <NSObject>
@optional
//为什么会把选中代理分为三个,因为有时候只关心点击选中的,有时候只关心滚动选中的,有时候只关心选中。所以具体情况,使用对应方法。
/**
点击选择或者滚动选中都会调用该方法,如果外部不关心具体是点击还是滚动选中的,只关心选中这个事件,就实现该方法。
@param categoryView categoryView description
@param index 选中的index
*/
- (void)categoryView:(JXCategoryBaseView *)categoryView didSelectedItemAtIndex:(NSInteger)index;
/**
点击选中的情况才会调用该方法
@param categoryView categoryView description
@param index 选中的index
*/
- (void)categoryView:(JXCategoryBaseView *)categoryView didClickSelectedItemAtIndex:(NSInteger)index;
/**
滚动选中的情况才会调用该方法
@param categoryView categoryView description
@param index 选中的index
*/
- (void)categoryView:(JXCategoryBaseView *)categoryView didScrollSelectedItemAtIndex:(NSInteger)index;
/**
只有点击的切换才会调用!!!
因为用户点击,contentScrollView即将过渡到目标index的配置。内部默认实现`[self.contentScrollView setContentOffset:CGPointMake(targetIndex*self.contentScrollView.bounds.size.width, 0) animated:YES];`。如果实现该代理方法,以自定义实现为准。比如将animated设置为NO,点击切换时无需滚动效果。类似于今日头条APP。
@param categoryView categoryView description
@param index index description
*/
- (void)categoryView:(JXCategoryBaseView *)categoryView didClickedItemContentScrollViewTransitionToIndex:(NSInteger)index;
/**
正在滚动中的回调
@param categoryView categoryView description
@param leftIndex 正在滚动中,相对位置处于左边的index
@param rightIndex 正在滚动中,相对位置处于右边的index
@param ratio 百分比
*/
- (void)categoryView:(JXCategoryBaseView *)categoryView scrollingFromLeftIndex:(NSInteger)leftIndex toRightIndex:(NSInteger)rightIndex ratio:(CGFloat)ratio;
@end
@interface JXCategoryBaseView : UIView
@property (nonatomic, strong) JXCategoryCollectionView *collectionView;
@property (nonatomic, strong) NSArray <JXCategoryBaseCellModel *>*dataSource;
@property (nonatomic, weak) id<JXCategoryViewDelegate>delegate;
@property (nonatomic, strong) UIScrollView *contentScrollView; //需要关联的contentScrollView
@property (nonatomic, assign) NSInteger defaultSelectedIndex; //修改初始化的时候默认选择的index
@property (nonatomic, assign, readonly) NSInteger selectedIndex;
@property (nonatomic, assign) CGFloat contentEdgeInsetLeft; //整体内容的左边距,默认JXCategoryViewAutomaticDimension(等于cellSpacing)
@property (nonatomic, assign) CGFloat contentEdgeInsetRight; //整体内容的右边距,默认JXCategoryViewAutomaticDimension(等于cellSpacing)
@property (nonatomic, assign) CGFloat cellWidth; //默认JXCategoryViewAutomaticDimension
@property (nonatomic, assign) CGFloat cellWidthIncrement; //cell宽度补偿。默认:0
@property (nonatomic, assign) CGFloat cellSpacing; //cell之间的间距,默认20
@property (nonatomic, assign) BOOL averageCellSpacingEnabled; //当item内容总宽度小于JXCategoryBaseView的宽度,是否将cellSpacing均分。默认为YES。
//----------------------cellWidthZoomEnabled-----------------------//
//cell宽度的缩放主要是为了腾讯视频、汽车之家效果打造的,一般情况下慎用,不太好控制。
@property (nonatomic, assign) BOOL cellWidthZoomEnabled; //默认为NO
@property (nonatomic, assign) BOOL cellWidthZoomScrollGradientEnabled; //手势滚动中,是否需要更新状态。默认为YES
@property (nonatomic, assign) CGFloat cellWidthZoomScale; //默认1.2,cellWidthZoomEnabled为YES才生效
/**
代码调用选中了目标index的item
@param index 目标index
*/
- (void)selectItemAtIndex:(NSInteger)index;
/**
初始化的时候无需调用。初始化之后更新其他配置属性,需要调用该方法,进行刷新。
*/
- (void)reloadData;
/**
刷新指定的index的cell
@param index 指定cell的index
*/
- (void)reloadCellAtIndex:(NSInteger)index;
#pragma mark - Subclass use
- (CGRect)getTargetCellFrame:(NSInteger)targetIndex;
#pragma mark - Subclass Override
- (void)initializeData NS_REQUIRES_SUPER;
- (void)initializeViews NS_REQUIRES_SUPER;
/**
reloadData方法调用,重新生成数据源赋值到self.dataSource
*/
- (void)refreshDataSource;
/**
reloadData方法调用,根据数据源重新刷新状态;
*/
- (void)refreshState NS_REQUIRES_SUPER;
/**
用户点击了某个item,刷新选中与取消选中的cellModel
@param selectedCellModel 选中的cellModel
@param unselectedCellModel 取消选中的cellModel
*/
- (void)refreshSelectedCellModel:(JXCategoryBaseCellModel *)selectedCellModel unselectedCellModel:(JXCategoryBaseCellModel *)unselectedCellModel NS_REQUIRES_SUPER;
/**
关联的contentScrollView的contentOffset发生了改变
@param contentOffset 偏移量
*/
- (void)contentOffsetOfContentScrollViewDidChanged:(CGPoint)contentOffset NS_REQUIRES_SUPER;
/**
该方法用于子类重载,如果外部要选中某个index,请使用`- (void)selectItemAtIndex:(NSUInteger)index;`
点击某一个item,或者contentScrollView滚动到某一个item的时候调用。根据selectIndex刷新选中状态。
@param index 选中的index
@param isClicked YES:点击选中;NO:滚动选中。
@return 返回值为NO,表示触发内部某些判断(点击了同一个cell),子类无需后续操作。
*/
- (BOOL)selectCellAtIndex:(NSInteger)index isClicked:(BOOL)isClicked NS_REQUIRES_SUPER;
/**
reloadData时,返回每个cell的宽度
@param index 目标index
@return cellWidth
*/
- (CGFloat)preferredCellWidthAtIndex:(NSInteger)index;
/**
返回自定义cell的class
@return cell class
*/
- (Class)preferredCellClass;
/**
refreshState时调用,重置cellModel的状态
@param cellModel 待重置的cellModel
@param index 目标index
*/
- (void)refreshCellModel:(JXCategoryBaseCellModel *)cellModel index:(NSInteger)index;
@end
//
// JXCategoryBaseView.m
// UI系列测试
//
// Created by jiaxin on 2018/3/15.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryBaseView.h"
#import "JXCategoryFactory.h"
struct DelegateFlags {
unsigned int didSelectedItemAtIndexFlag : 1;
unsigned int didClickSelectedItemAtIndexFlag : 1;
unsigned int didScrollSelectedItemAtIndexFlag : 1;
unsigned int didClickedItemContentScrollViewTransitionToIndexFlag : 1;
unsigned int scrollingFromLeftIndexToRightIndexFlag : 1;
};
@interface JXCategoryBaseView () <UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>
@property (nonatomic, assign) struct DelegateFlags delegateFlags;
@property (nonatomic, assign) NSInteger selectedIndex;
@property (nonatomic, assign) CGFloat innerCellSpacing;
@property (nonatomic, assign) CGPoint lastContentViewContentOffset;
@end
@implementation JXCategoryBaseView
- (void)dealloc
{
if (self.contentScrollView) {
[self.contentScrollView removeObserver:self forKeyPath:@"contentOffset"];
}
}
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self initializeData];
[self initializeViews];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self initializeData];
[self initializeViews];
}
return self;
}
- (void)setDelegate:(id<JXCategoryViewDelegate>)delegate {
_delegate = delegate;
_delegateFlags.didSelectedItemAtIndexFlag = [delegate respondsToSelector:@selector(categoryView:didSelectedItemAtIndex:)];
_delegateFlags.didClickSelectedItemAtIndexFlag = [delegate respondsToSelector:@selector(categoryView:didClickSelectedItemAtIndex:)];
_delegateFlags.didScrollSelectedItemAtIndexFlag = [delegate respondsToSelector:@selector(categoryView:didScrollSelectedItemAtIndex:)];
_delegateFlags.didClickedItemContentScrollViewTransitionToIndexFlag = [delegate respondsToSelector:@selector(categoryView:didClickedItemContentScrollViewTransitionToIndex:)];
_delegateFlags.scrollingFromLeftIndexToRightIndexFlag = [delegate respondsToSelector:@selector(categoryView:scrollingFromLeftIndex:toRightIndex:ratio:)];
}
- (void)initializeData
{
_dataSource = [NSMutableArray array];
_selectedIndex = 0;
_cellWidth = JXCategoryViewAutomaticDimension;
_cellWidthIncrement = 0;
_cellSpacing = 20;
_averageCellSpacingEnabled = YES;
_cellWidthZoomEnabled = NO;
_cellWidthZoomScale = 1.2;
_cellWidthZoomScrollGradientEnabled = YES;
_contentEdgeInsetLeft = JXCategoryViewAutomaticDimension;
_contentEdgeInsetRight = JXCategoryViewAutomaticDimension;
_lastContentViewContentOffset = CGPointZero;
}
- (void)initializeViews
{
self.collectionView = ({
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
JXCategoryCollectionView *collectionView = [[JXCategoryCollectionView alloc] initWithFrame:self.bounds collectionViewLayout:layout];
collectionView.backgroundColor = [UIColor clearColor];
collectionView.showsHorizontalScrollIndicator = NO;
collectionView.showsVerticalScrollIndicator = NO;
collectionView.scrollsToTop = NO;
collectionView.dataSource = self;
collectionView.delegate = self;
[collectionView registerClass:[self preferredCellClass] forCellWithReuseIdentifier:NSStringFromClass([self preferredCellClass])];
collectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
collectionView;
});
[self addSubview:self.collectionView];
}
- (void)setDefaultSelectedIndex:(NSInteger)defaultSelectedIndex
{
_defaultSelectedIndex = defaultSelectedIndex;
self.selectedIndex = defaultSelectedIndex;
}
- (void)setContentScrollView:(UIScrollView *)contentScrollView
{
if (_contentScrollView != nil) {
[_contentScrollView removeObserver:self forKeyPath:@"contentOffset"];
}
_contentScrollView = contentScrollView;
_contentScrollView.scrollsToTop = NO;
[contentScrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
}
- (void)reloadData {
[self refreshDataSource];
[self refreshState];
[self.collectionView.collectionViewLayout invalidateLayout];
[self.collectionView reloadData];
}
- (void)reloadCellAtIndex:(NSInteger)index {
if (index >= self.dataSource.count) {
return;
}
JXCategoryBaseCellModel *cellModel = self.dataSource[index];
[self refreshCellModel:cellModel index:index];
JXCategoryBaseCell *cell = (JXCategoryBaseCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]];
[cell reloadData:cellModel];
}
- (void)selectItemAtIndex:(NSInteger)index {
[self selectCellAtIndex:index isClicked:YES];
}
- (void)layoutSubviews
{
[super layoutSubviews];
[self reloadData];
}
#pragma mark - Subclass Override
- (void)refreshDataSource {
}
- (void)refreshState {
if (self.selectedIndex >= self.dataSource.count) {
self.selectedIndex = 0;
}
self.innerCellSpacing = self.cellSpacing;
__block CGFloat totalItemWidth = [self getContentEdgeInsetLeft];
CGFloat totalCellWidth = 0;
for (int i = 0; i < self.dataSource.count; i++) {
JXCategoryBaseCellModel *cellModel = self.dataSource[i];
cellModel.index = i;
cellModel.cellWidth = [self preferredCellWidthAtIndex:i] + self.cellWidthIncrement;
totalCellWidth += cellModel.cellWidth;
cellModel.cellWidthZoomEnabled = self.cellWidthZoomEnabled;
cellModel.cellWidthZoomScale = 1.0;
cellModel.cellSpacing = self.cellSpacing;
if (i == self.dataSource.count - 1) {
totalItemWidth += cellModel.cellWidth + [self getContentEdgeInsetRight];
}else {
totalItemWidth += cellModel.cellWidth + self.cellSpacing;
}
if (i == self.selectedIndex) {
cellModel.selected = YES;
cellModel.cellWidthZoomScale = self.cellWidthZoomScale;
}else {
cellModel.selected = NO;
}
[self refreshCellModel:cellModel index:i];
}
if (self.averageCellSpacingEnabled && totalItemWidth < self.bounds.size.width) {
//如果总的内容宽度都没有超过视图度,就将cellWidth等分
NSInteger cellSpacingItemCount = self.dataSource.count - 1;
CGFloat totalCellSpacingWidth = self.bounds.size.width - totalCellWidth;
//如果内容左边距是Automatic,就加1
if (self.contentEdgeInsetLeft == JXCategoryViewAutomaticDimension) {
cellSpacingItemCount += 1;
}else {
totalCellSpacingWidth -= self.contentEdgeInsetLeft;
}
//如果内容右边距是Automatic,就加1
if (self.contentEdgeInsetRight == JXCategoryViewAutomaticDimension) {
cellSpacingItemCount += 1;
}else {
totalCellSpacingWidth -= self.contentEdgeInsetRight;
}
CGFloat cellSpacing = 0;
if (cellSpacingItemCount > 0) {
cellSpacing = totalCellSpacingWidth/cellSpacingItemCount;
}
self.innerCellSpacing = cellSpacing;
[self.dataSource enumerateObjectsUsingBlock:^(JXCategoryBaseCellModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
obj.cellSpacing = cellSpacing;
}];
}
__block CGFloat frameXOfSelectedCell = self.innerCellSpacing;
__block CGFloat selectedCellWidth = 0;
totalItemWidth = self.innerCellSpacing;
[self.dataSource enumerateObjectsUsingBlock:^(JXCategoryBaseCellModel * cellModel, NSUInteger idx, BOOL * _Nonnull stop) {
if (idx < self.selectedIndex) {
frameXOfSelectedCell += cellModel.cellWidth + self.innerCellSpacing;
}else if (idx == self.selectedIndex) {
selectedCellWidth = cellModel.cellWidth;
}
totalItemWidth += cellModel.cellWidth + self.innerCellSpacing;
}];
CGFloat minX = 0;
CGFloat maxX = totalItemWidth - self.bounds.size.width;
CGFloat targetX = frameXOfSelectedCell - self.bounds.size.width/2.0 + selectedCellWidth/2.0;
[self.collectionView setContentOffset:CGPointMake(MAX(MIN(maxX, targetX), minX), 0) animated:NO];
if (CGRectEqualToRect(self.contentScrollView.frame, CGRectZero) && self.contentScrollView.superview != nil) {
//某些情况、系统会出现JXCategoryView先布局,contentScrollView后布局。就会导致下面指定defaultSelectedIndex失效,所以发现frame为zero时,强行触发布局。
[self.contentScrollView.superview setNeedsLayout];
[self.contentScrollView.superview layoutIfNeeded];
}
[self.contentScrollView setContentOffset:CGPointMake(self.selectedIndex*self.contentScrollView.bounds.size.width, 0) animated:NO];
}
- (BOOL)selectCellAtIndex:(NSInteger)targetIndex isClicked:(BOOL)isClicked {
return [self _selectCellAtIndex:targetIndex isClicked:isClicked];
}
- (BOOL)_selectCellAtIndex:(NSInteger)targetIndex isClicked:(BOOL)isClicked{
if (targetIndex >= self.dataSource.count) {
return NO;
}
if (self.selectedIndex == targetIndex) {
if (isClicked) {
if (self.delegateFlags.didClickSelectedItemAtIndexFlag) {
[self.delegate categoryView:self didClickSelectedItemAtIndex:targetIndex];
}
}else {
if (self.delegateFlags.didScrollSelectedItemAtIndexFlag) {
[self.delegate categoryView:self didScrollSelectedItemAtIndex:targetIndex];
}
}
if (self.delegateFlags.didSelectedItemAtIndexFlag) {
[self.delegate categoryView:self didSelectedItemAtIndex:targetIndex];
}
return NO;
}
JXCategoryBaseCellModel *lastCellModel = self.dataSource[self.selectedIndex];
JXCategoryBaseCellModel *selectedCellModel = self.dataSource[targetIndex];
[self refreshSelectedCellModel:selectedCellModel unselectedCellModel:lastCellModel];
JXCategoryBaseCell *lastCell = (JXCategoryBaseCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:self.selectedIndex inSection:0]];
[lastCell reloadData:lastCellModel];
JXCategoryBaseCell *selectedCell = (JXCategoryBaseCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:targetIndex inSection:0]];
[selectedCell reloadData:selectedCellModel];
if (self.cellWidthZoomEnabled) {
[self.collectionView.collectionViewLayout invalidateLayout];
//延时为了解决cellwidth变化,点击最后几个cell,scrollToItem会出现位置偏移bug
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:targetIndex inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
});
}else {
[self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:targetIndex inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
}
if (isClicked) {
if (self.delegateFlags.didClickedItemContentScrollViewTransitionToIndexFlag) {
[self.delegate categoryView:self didClickedItemContentScrollViewTransitionToIndex:targetIndex];
}else {
[self.contentScrollView setContentOffset:CGPointMake(targetIndex*self.contentScrollView.bounds.size.width, 0) animated:YES];
}
}
self.selectedIndex = targetIndex;
if (isClicked) {
if (self.delegateFlags.didClickSelectedItemAtIndexFlag) {
[self.delegate categoryView:self didClickSelectedItemAtIndex:targetIndex];
}
}else {
if (self.delegateFlags.didScrollSelectedItemAtIndexFlag) {
[self.delegate categoryView:self didScrollSelectedItemAtIndex:targetIndex];
}
}
if (self.delegateFlags.didSelectedItemAtIndexFlag) {
[self.delegate categoryView:self didSelectedItemAtIndex:targetIndex];
}
return YES;
}
- (void)refreshSelectedCellModel:(JXCategoryBaseCellModel *)selectedCellModel unselectedCellModel:(JXCategoryBaseCellModel *)unselectedCellModel {
selectedCellModel.selected = YES;
selectedCellModel.cellWidthZoomScale = self.cellWidthZoomScale;
unselectedCellModel.selected = NO;
unselectedCellModel.cellWidthZoomScale = 1.0;
}
- (void)contentOffsetOfContentScrollViewDidChanged:(CGPoint)contentOffset {
CGFloat ratio = contentOffset.x/self.contentScrollView.bounds.size.width;
if (ratio > self.dataSource.count - 1 || ratio < 0) {
//超过了边界,不需要处理
return;
}
if (contentOffset.x == 0 && self.selectedIndex == 0 && self.lastContentViewContentOffset.x == 0) {
//滚动到了最左边,且已经选中了第一个,且之前的contentOffset.x为0
return;
}
CGFloat maxContentOffsetX = self.contentScrollView.contentSize.width - self.contentScrollView.bounds.size.width;
if (contentOffset.x == maxContentOffsetX && self.selectedIndex == self.dataSource.count - 1 && self.lastContentViewContentOffset.x == maxContentOffsetX) {
//滚动到了最右边,且已经选中了最后一个,且之前的contentOffset.x为maxContentOffsetX
return;
}
ratio = MAX(0, MIN(self.dataSource.count - 1, ratio));
NSInteger baseIndex = floorf(ratio);
CGFloat remainderRatio = ratio - baseIndex;
if (remainderRatio == 0) {
//滑动翻页,需要更新选中状态
//滑动一小段距离,然后放开回到原位,contentOffset同样的值会回调多次。例如在index为1的情况,滑动放开回到原位,contentOffset会多次回调CGPoint(width, 0)
if (!(self.lastContentViewContentOffset.x == contentOffset.x && self.selectedIndex == baseIndex)) {
[self scrollselectItemAtIndex:baseIndex];
}
}else {
//快速滑动翻页,当remainderRatio没有变成0,但是已经翻页了,需要通过下面的判断,触发选中
if (fabs(ratio - self.selectedIndex) > 1) {
NSInteger targetIndex = baseIndex;
if (ratio < self.selectedIndex) {
targetIndex = baseIndex + 1;
}
[self _selectCellAtIndex:targetIndex isClicked:NO];
}
if (self.cellWidthZoomEnabled && self.cellWidthZoomScrollGradientEnabled) {
JXCategoryBaseCellModel *leftCellModel = (JXCategoryBaseCellModel *)self.dataSource[baseIndex];
JXCategoryBaseCellModel *rightCellModel = (JXCategoryBaseCellModel *)self.dataSource[baseIndex + 1];
leftCellModel.cellWidthZoomScale = [JXCategoryFactory interpolationFrom:self.cellWidthZoomScale to:1.0 percent:remainderRatio];
rightCellModel.cellWidthZoomScale = [JXCategoryFactory interpolationFrom:1.0 to:self.cellWidthZoomScale percent:remainderRatio];
[self.collectionView.collectionViewLayout invalidateLayout];
}
if (self.delegateFlags.scrollingFromLeftIndexToRightIndexFlag) {
[self.delegate categoryView:self scrollingFromLeftIndex:baseIndex toRightIndex:baseIndex + 1 ratio:remainderRatio];
}
}
}
- (CGFloat)preferredCellWidthAtIndex:(NSInteger)index {
return 0;
}
- (Class)preferredCellClass {
return JXCategoryBaseCell.class;
}
- (void)refreshCellModel:(JXCategoryBaseCellModel *)cellModel index:(NSInteger)index {
}
#pragma mark - <UICollectionViewDataSource, UICollectionViewDelegate>
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.dataSource.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
return [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([self preferredCellClass]) forIndexPath:indexPath];
}
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
JXCategoryBaseCell *categoryCell = (JXCategoryBaseCell *)cell;
[categoryCell reloadData:self.dataSource[indexPath.item]];
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
[self clickselectItemAtIndex:indexPath.row];
}
#pragma mark - <UICollectionViewDelegateFlowLayout>
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(0, [self getContentEdgeInsetLeft], 0, [self getContentEdgeInsetRight]);
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
JXCategoryBaseCellModel *cellModel = self.dataSource[indexPath.item];
return CGSizeMake(cellModel.cellWidth, self.bounds.size.height);
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
return self.innerCellSpacing;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
return self.innerCellSpacing;
}
#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if ([keyPath isEqualToString:@"contentOffset"]) {
CGPoint contentOffset = [change[NSKeyValueChangeNewKey] CGPointValue];
if ((self.contentScrollView.isTracking || self.contentScrollView.isDecelerating)) {
//用户滚动引起的contentOffset变化,才处理。
[self contentOffsetOfContentScrollViewDidChanged:contentOffset];
}
self.lastContentViewContentOffset = contentOffset;
}
}
#pragma mark - Other
- (CGRect)getTargetCellFrame:(NSInteger)targetIndex
{
CGFloat x = [self getContentEdgeInsetLeft];
for (int i = 0; i < targetIndex; i ++) {
JXCategoryBaseCellModel *cellModel = self.dataSource[i];
x += cellModel.cellWidth + self.innerCellSpacing;
}
CGFloat width = self.dataSource[targetIndex].cellWidth;
return CGRectMake(x, 0, width, self.bounds.size.height);
}
#pragma mark - Private
- (CGFloat)getContentEdgeInsetLeft {
if (self.contentEdgeInsetLeft == JXCategoryViewAutomaticDimension) {
return self.innerCellSpacing;
}
return self.contentEdgeInsetLeft;
}
- (CGFloat)getContentEdgeInsetRight {
if (self.contentEdgeInsetRight == JXCategoryViewAutomaticDimension) {
return self.innerCellSpacing;
}
return self.contentEdgeInsetRight;
}
- (void)clickselectItemAtIndex:(NSInteger)index {
[self selectCellAtIndex:index isClicked:YES];
}
- (void)scrollselectItemAtIndex:(NSInteger)index {
[self selectCellAtIndex:index isClicked:NO];
}
@end
//
// JXCategoryCollectionView.h
// UI系列测试
//
// Created by jiaxin on 2018/3/21.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "JXCategoryIndicatorProtocol.h"
@interface JXCategoryCollectionView : UICollectionView
@property (nonatomic, strong) NSArray <UIView<JXCategoryIndicatorProtocol> *> *indicators;
@end
//
// JXCategoryCollectionView.m
// UI系列测试
//
// Created by jiaxin on 2018/3/21.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryCollectionView.h"
@implementation JXCategoryCollectionView
- (void)layoutSubviews
{
[super layoutSubviews];
for (UIView<JXCategoryIndicatorProtocol> *view in self.indicators) {
[self sendSubviewToBack:view];
}
}
@end
//
// JXCategoryFactory.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface JXCategoryFactory : NSObject
+ (CGFloat)interpolationFrom:(CGFloat)from to:(CGFloat)to percent:(CGFloat)percent;
+ (UIColor *)interpolationColorFrom:(UIColor *)fromColor to:(UIColor *)toColor percent:(CGFloat)percent;
@end
//
// JXCategoryFactory.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryFactory.h"
#import "UIColor+JXAdd.h"
@implementation JXCategoryFactory
+ (CGFloat)interpolationFrom:(CGFloat)from to:(CGFloat)to percent:(CGFloat)percent
{
percent = MAX(0, MIN(1, percent));
NSLog(@"percent---%.2f", percent);
NSLog(@"from + (to - from)*---%.2f", (from + (to - from)*percent));
return from + (to - from)*percent;
}
+ (UIColor *)interpolationColorFrom:(UIColor *)fromColor to:(UIColor *)toColor percent:(CGFloat)percent
{
CGFloat red = [self interpolationFrom:fromColor.jx_red to:toColor.jx_red percent:percent];
CGFloat green = [self interpolationFrom:fromColor.jx_green to:toColor.jx_green percent:percent];
CGFloat blue = [self interpolationFrom:fromColor.jx_blue to:toColor.jx_blue percent:percent];
CGFloat alpha = [self interpolationFrom:fromColor.jx_alpha to:toColor.jx_alpha percent:percent];
return [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
}
@end
//
// JXCategoryIndicatorParamsModel.h
// JXCategoryView
//
// Created by jiaxin on 2018/12/13.
// Copyright © 2018 jiaxin. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
/**
指示器不同情况处理时传递的数据模型,不同情况会对不同的属性赋值,根据不同情况的api说明确认。
为什么会通过model传递数据,因为指示器处理逻辑以后会扩展不同的使用场景,会新增参数,如果不通过model传递,就会在api新增参数,一旦修改api该的地方就特别多了,而且会影响到之前自定义实现的开发者。
*/
@interface JXCategoryIndicatorParamsModel : NSObject
@property (nonatomic, assign) NSInteger selectedIndex; //当前选中的index
@property (nonatomic, assign) CGRect selectedCellFrame; //当前选中的cellFrame
@property (nonatomic, assign) NSInteger leftIndex; //正在过渡中的两个cell,相对位置在左边的cell的index
@property (nonatomic, assign) CGRect leftCellFrame; //正在过渡中的两个cell,相对位置在左边的cell的frame
@property (nonatomic, assign) NSInteger rightIndex; //正在过渡中的两个cell,相对位置在右边的cell的index
@property (nonatomic, assign) CGRect rightCellFrame; //正在过渡中的两个cell,相对位置在右边的cell的frame
@property (nonatomic, assign) CGFloat percent; //正在过渡中的两个cell,从左到右的百分比
@property (nonatomic, assign) NSInteger lastSelectedIndex; //之前选中的index
@property (nonatomic, assign) BOOL isClicked; //YES:通过点击选中;NO:通过滚动选中
@end
//
// JXCategoryIndicatorParamsModel.m
// JXCategoryView
//
// Created by jiaxin on 2018/12/13.
// Copyright © 2018 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorParamsModel.h"
@implementation JXCategoryIndicatorParamsModel
@end
//
// JXCategoryIndicatorProtocol.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "JXCategoryViewDefines.h"
#import "JXCategoryIndicatorParamsModel.h"
@protocol JXCategoryIndicatorProtocol <NSObject>
/**
视图重置状态时调用
param selectedIndex 当前选中的index
param selectedCellFrame 当前选中的cellFrame
@param model 数据模型
*/
- (void)jx_refreshState:(JXCategoryIndicatorParamsModel *)model;
/**
contentScrollView在进行手势滑动时,处理指示器跟随手势变化UI逻辑;
param selectedIndex 当前选中的index
param leftIndex 正在过渡中的两个cell,相对位置在左边的cell的index
param leftCellFrame 正在过渡中的两个cell,相对位置在左边的cell的frame
param rightIndex 正在过渡中的两个cell,相对位置在右边的cell的index
param rightCellFrame 正在过渡中的两个cell,相对位置在右边的cell的frame
param percent 过渡百分比
@param model 数据模型
*/
- (void)jx_contentScrollViewDidScroll:(JXCategoryIndicatorParamsModel *)model;
/**
点击选中了某一个cell
param lastSelectedIndex 之前选中的index
param selectedIndex 选中的index
param selectedCellFrame 选中的cellFrame
param isClicked YES:点击选中;NO:滚动选中。
@param model 数据模型
*/
- (void)jx_selectedCell:(JXCategoryIndicatorParamsModel *)model;
@end
//
// JXCategoryListScrollView.h
// JXCategoryView
//
// Created by jiaxin on 2018/9/12.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
@class JXCategoryListContainerView;
@protocol JXCategoryListContentViewDelegate <NSObject>
/**
如果列表是VC,就返回VC.view
如果列表是View,就返回View自己
@return 返回列表视图
*/
- (UIView *)listView;
@optional
/**
可选实现,列表逻辑层面显示的时候调用
*/
- (void)listDidAppear;
/**
可选实现,列表逻辑层面消失的时候调用
*/
- (void)listDidDisappear;
@end
@protocol JXCategoryListContainerViewDelegate <NSObject>
/**
返回list的数量
@param listContainerView 列表的容器视图
@return list的数量
*/
- (NSInteger)numberOfListsInlistContainerView:(JXCategoryListContainerView *)listContainerView;
/**
根据index初始化对应的遵从JXCategoryListContentViewDelegate协议的list,注意是初始化哟,要new一个新的实例!!!
@param listContainerView 列表的容器视图
@param index 目标下标
@return 新的遵从JXCategoryListContentViewDelegate协议的list实例
*/
- (id<JXCategoryListContentViewDelegate>)listContainerView:(JXCategoryListContainerView *)listContainerView initListForIndex:(NSInteger)index;
@end
@interface JXCategoryListContainerView : UIView
@property (nonatomic, strong, readonly) UIScrollView *scrollView;
/**
滚动切换的时候,滚动距离超过一页的多少百分比,就认为切换了页面。默认0.5(即滚动超过了半屏,就认为翻页了)。范围0~1,开区间不包括0和1
*/
@property (nonatomic, assign) CGFloat didAppearPercent;
/**
需要和self.categoryView.defaultSelectedIndex保持一致
*/
@property (nonatomic, assign) NSInteger defaultSelectedIndex;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
/**
指定的初始化器
@param parentVC 父vc
@param delegate JXCategoryListContainerViewDelegate代理
@return JXCategoryListContainerView实例
*/
- (instancetype)initWithParentVC:(UIViewController *)parentVC delegate:(id<JXCategoryListContainerViewDelegate>)delegate NS_DESIGNATED_INITIALIZER;
- (void)reloadData;
//必须调用,请按照demo示例那样调用
- (void)scrollingFromLeftIndex:(NSInteger)leftIndex toRightIndex:(NSInteger)rightIndex ratio:(CGFloat)ratio selectedIndex:(NSInteger)selectedIndex;
//必须调用,请按照demo示例那样调用(注意是是点击选中的回调,不是其他回调)
- (void)didClickSelectedItemAtIndex:(NSInteger)index;
@end
//
// JXCategoryListContainerView.m
// JXCategoryView
//
// Created by jiaxin on 2018/9/12.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryListContainerView.h"
@interface JXCategoryListContainerView ()
@property (nonatomic, weak) id<JXCategoryListContainerViewDelegate> delegate;
@property (nonatomic, weak) UIViewController *parentViewController;
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, assign) NSInteger currentIndex;
@property (nonatomic, strong) NSMutableDictionary <NSNumber *, id<JXCategoryListContentViewDelegate>> *listVCDict;
@property (nonatomic, assign) BOOL isLayoutSubviewsed;
@end
@implementation JXCategoryListContainerView
- (instancetype)initWithParentVC:(UIViewController *)parentVC delegate:(id<JXCategoryListContainerViewDelegate>)delegate {
self = [super initWithFrame:CGRectZero];
if (self) {
_didAppearPercent = 0.5;
_parentViewController = parentVC;
_parentViewController.automaticallyAdjustsScrollViewInsets = NO;
_delegate = delegate;
_listVCDict = [NSMutableDictionary dictionary];
[self initializeViews];
}
return self;
}
- (void)initializeViews {
_scrollView = [[UIScrollView alloc] init];
self.scrollView.pagingEnabled = YES;
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.scrollsToTop = NO;
self.scrollView.bounces = NO;
if (@available(iOS 11.0, *)) {
self.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
[self addSubview:self.scrollView];
}
- (void)reloadData {
for (id<JXCategoryListContentViewDelegate> list in self.listVCDict.allValues) {
[list.listView removeFromSuperview];
}
[self.listVCDict removeAllObjects];
self.scrollView.contentSize = CGSizeMake(self.scrollView.bounds.size.width*[self.delegate numberOfListsInlistContainerView:self], self.scrollView.bounds.size.height);
[self listDidAppear:self.currentIndex];
}
- (void)layoutSubviews {
[super layoutSubviews];
self.scrollView.frame = self.bounds;
self.scrollView.contentSize = CGSizeMake(self.scrollView.bounds.size.width*[self.delegate numberOfListsInlistContainerView:self], self.scrollView.bounds.size.height);
if (!self.isLayoutSubviewsed) {
self.isLayoutSubviewsed = YES;
//初始化第一次调用
[self listDidAppear:self.currentIndex];
}
}
- (void)setDefaultSelectedIndex:(NSInteger)defaultSelectedIndex {
_defaultSelectedIndex = defaultSelectedIndex;
self.currentIndex = defaultSelectedIndex;
}
#pragma mark - JXCategoryBaseView回调
- (void)scrollingFromLeftIndex:(NSInteger)leftIndex toRightIndex:(NSInteger)rightIndex ratio:(CGFloat)ratio selectedIndex:(NSInteger)selectedIndex {
NSInteger targetIndex = -1;
NSInteger disappearIndex = -1;
if (rightIndex == selectedIndex) {
//当前选中的在右边,用户正在从右边往左边滑动
if (ratio < (1 - self.didAppearPercent)) {
targetIndex = leftIndex;
disappearIndex = rightIndex;
}
}else {
//当前选中的在左边,用户正在从左边往右边滑动
if (ratio > self.didAppearPercent) {
targetIndex = rightIndex;
disappearIndex = leftIndex;
}
}
if (targetIndex != -1 && self.currentIndex != targetIndex) {
[self listDidAppear:targetIndex];
[self listDidDisappear:disappearIndex];
}
}
- (void)didClickSelectedItemAtIndex:(NSInteger)index {
[self listDidDisappear:self.currentIndex];
[self listDidAppear:index];
}
#pragma mark - Private
- (void)listDidAppear:(NSInteger)index {
self.currentIndex = index;
id<JXCategoryListContentViewDelegate> list = self.listVCDict[@(index)];
if (list == nil) {
list = [self.delegate listContainerView:self initListForIndex:index];
}
if (list.listView.superview == nil) {
list.listView.frame = CGRectMake(index*self.scrollView.bounds.size.width, 0, self.scrollView.bounds.size.width, self.scrollView.bounds.size.height);
[self.scrollView addSubview:list.listView];
self.listVCDict[@(index)] = list;
}
if (list && [list respondsToSelector:@selector(listDidAppear)]) {
[list listDidAppear];
}
}
- (void)listDidDisappear:(NSInteger)index {
id<JXCategoryListContentViewDelegate> list = self.listVCDict[@(index)];
if (list && [list respondsToSelector:@selector(listDidDisappear)]) {
[list listDidDisappear];
}
}
@end
//
// JXCategoryListScrollView.h
// JXCategoryView
//
// Created by jiaxin on 2018/9/12.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
/**
此类已不再维护,推荐使用JXCategoryListContainerView类!!!
*/
@interface JXCategoryListVCContainerView : UIView
@property (nonatomic, weak) UIViewController *parentViewController;
@property (nonatomic, strong, readonly) UIScrollView *scrollView;
@property (nonatomic, strong) NSArray <UIViewController *> *listVCArray;
//这个defaultSelectedIndex仅仅用于触发对应index的数据加载,如果要让categoryView和listView都处于对应的index。还应该添加后面这段代码:self.categoryView.defaultSelectedIndex = n
@property (nonatomic, assign) NSInteger defaultSelectedIndex;
- (void)reloadData;
- (void)scrollingFromLeftIndex:(NSInteger)leftIndex toRightIndex:(NSInteger)rightIndex ratio:(CGFloat)ratio;
- (void)didClickSelectedItemAtIndex:(NSInteger)index;
- (void)didScrollSelectedItemAtIndex:(NSInteger)index;
- (void)parentVCWillAppear:(BOOL)animated;
- (void)parentVCDidAppear:(BOOL)animated;
- (void)parentVCWillDisappear:(BOOL)animated;
- (void)parentVCDidDisappear:(BOOL)animated;
@end
//
// JXCategoryListContainerView.m
// JXCategoryView
//
// Created by jiaxin on 2018/9/12.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryListVCContainerView.h"
@interface JXCategoryListVCContainerView () <UIScrollViewDelegate>
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, assign) NSInteger currentIndex;
@property (nonatomic, assign) NSInteger targetIndex;
@end
@implementation JXCategoryListVCContainerView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self initializeViews];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self initializeViews];
}
return self;
}
- (void)initializeViews {
_scrollView = [[UIScrollView alloc] init];
self.scrollView.pagingEnabled = YES;
self.scrollView.showsHorizontalScrollIndicator = NO;
[self addSubview:self.scrollView];
}
- (void)reloadData {
[self listVCWillAppear:self.defaultSelectedIndex];
[self listVCDidAppear:self.defaultSelectedIndex];
}
- (void)layoutSubviews {
[super layoutSubviews];
self.scrollView.frame = self.bounds;
//根据新的数据源重新添加listView
for (int i = 0; i < self.listVCArray.count; i ++) {
UIViewController *listVC = self.listVCArray[i];
[listVC willMoveToParentViewController:self.parentViewController];
[self.parentViewController addChildViewController:self.parentViewController];
[listVC didMoveToParentViewController:self.parentViewController];
listVC.view.frame = CGRectMake(i*self.scrollView.bounds.size.width, 0, self.scrollView.bounds.size.width, self.scrollView.bounds.size.height);
[self.scrollView addSubview:listVC.view];
}
self.scrollView.contentSize = CGSizeMake(self.scrollView.bounds.size.width*self.listVCArray.count, self.scrollView.bounds.size.height);
}
- (void)setListVCArray:(NSArray<UIViewController *> *)listVCArray {
for (UIViewController *vc in self.listVCArray) {
[vc.view removeFromSuperview];
}
_listVCArray = listVCArray;
[self setNeedsLayout];
}
- (void)setDefaultSelectedIndex:(NSInteger)defaultSelectedIndex {
_defaultSelectedIndex = defaultSelectedIndex;
self.currentIndex = defaultSelectedIndex;
}
- (void)parentVCWillAppear:(BOOL)animated {
[self listVCWillAppear:self.currentIndex];
}
- (void)parentVCDidAppear:(BOOL)animated {
[self listVCDidAppear:self.currentIndex];
}
- (void)parentVCWillDisappear:(BOOL)animated {
[self listVCWillDisappear:self.currentIndex];
}
- (void)parentVCDidDisappear:(BOOL)animated {
[self listVCDidDisappear:self.currentIndex];
}
- (void)scrollingFromLeftIndex:(NSInteger)leftIndex toRightIndex:(NSInteger)rightIndex ratio:(CGFloat)ratio {
NSInteger targetIndex = 0;
if (ratio > 0.5) {
targetIndex = rightIndex;
}else {
targetIndex = leftIndex;
}
if (self.targetIndex != targetIndex) {
[self listVCWillAppear:targetIndex];
[self listVCDidAppear:targetIndex];
//源代码
//[self listVCWillDisappear:self.currentIndex];
//[self listVCDidDisappear:self.currentIndex];
//add by wuna 解决了部分生命周期问题和标题跟页面正确对应问题
if (ratio > 0.5) {
[self listVCWillDisappear:leftIndex];
[self listVCDidDisappear:leftIndex];
}else{
[self listVCWillDisappear:rightIndex];
[self listVCDidDisappear:rightIndex];
}
}
}
- (void)didScrollSelectedItemAtIndex:(NSInteger)index {
}
- (void)didClickSelectedItemAtIndex:(NSInteger)index {
[self listVCWillDisappear:self.currentIndex];
[self listVCDidDisappear:self.currentIndex];
[self listVCWillAppear:index];
[self listVCDidAppear:index];
}
#pragma mark - Private
- (void)listVCWillAppear:(NSInteger)index {
self.targetIndex = index;
UIViewController *vc = self.listVCArray[index];
[vc beginAppearanceTransition:YES animated:YES];
}
- (void)listVCDidAppear:(NSInteger)index {
self.currentIndex = index;
UIViewController *vc = self.listVCArray[index];
[vc endAppearanceTransition];
}
- (void)listVCWillDisappear:(NSInteger)index {
UIViewController *vc = self.listVCArray[index];
[vc beginAppearanceTransition:NO animated:YES];
}
- (void)listVCDidDisappear:(NSInteger)index {
UIViewController *vc = self.listVCArray[index];
[vc endAppearanceTransition];
}
@end
//
// JXCategoryViewDefines.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
static const CGFloat JXCategoryViewAutomaticDimension = -1;
typedef NS_ENUM(NSUInteger, JXCategoryComponentPosition) {
JXCategoryComponentPosition_Bottom,
JXCategoryComponentPosition_Top,
};
typedef NS_ENUM(NSUInteger, JXCategoryIndicatorScrollStyle) {
JXCategoryIndicatorScrollStyleSimple, //简单滚动,即从当前位置过渡到目标位置
JXCategoryIndicatorScrollStyleSameAsUserScroll, //和用户左右滚动列表时的效果一样
};
//
// UIColor+JXAdd.h
// UI系列测试
//
// Created by jiaxin on 2018/3/21.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UIColor (JXAdd)
@property (nonatomic, assign, readonly) CGFloat jx_red;
@property (nonatomic, assign, readonly) CGFloat jx_green;
@property (nonatomic, assign, readonly) CGFloat jx_blue;
@property (nonatomic, assign, readonly) CGFloat jx_alpha;
@end
//
// UIColor+JXAdd.m
// UI系列测试
//
// Created by jiaxin on 2018/3/21.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "UIColor+JXAdd.h"
@implementation UIColor (JXAdd)
- (CGFloat)jx_red {
CGFloat r = 0, g, b, a;
[self getRed:&r green:&g blue:&b alpha:&a];
return r;
}
- (CGFloat)jx_green {
CGFloat r, g = 0, b, a;
[self getRed:&r green:&g blue:&b alpha:&a];
return g;
}
- (CGFloat)jx_blue {
CGFloat r, g, b = 0, a;
[self getRed:&r green:&g blue:&b alpha:&a];
return b;
}
- (CGFloat)jx_alpha {
return CGColorGetAlpha(self.CGColor);
}
@end
//
// JXCategoryDotCell.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/20.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryTitleCell.h"
@interface JXCategoryDotCell : JXCategoryTitleCell
@end
//
// JXCategoryDotCell.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/20.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryDotCell.h"
#import "JXCategoryDotCellModel.h"
@interface JXCategoryDotCell ()
@property (nonatomic, strong) CALayer *dotLayer;
@end
@implementation JXCategoryDotCell
- (void)initializeViews {
[super initializeViews];
_dotLayer = [CALayer layer];
[self.contentView.layer addSublayer:self.dotLayer];
}
- (void)layoutSubviews {
[super layoutSubviews];
[CATransaction begin];
[CATransaction setDisableActions:YES];
JXCategoryDotCellModel *myCellModel = (JXCategoryDotCellModel *)self.cellModel;
self.dotLayer.bounds = CGRectMake(0, 0, myCellModel.dotSize.width, myCellModel.dotSize.height);
switch (myCellModel.relativePosition) {
case JXCategoryDotRelativePosition_TopLeft:
{
self.dotLayer.position = CGPointMake(CGRectGetMinX(self.titleLabel.frame), CGRectGetMinY(self.titleLabel.frame));
}
break;
case JXCategoryDotRelativePosition_TopRight:
{
self.dotLayer.position = CGPointMake(CGRectGetMaxX(self.titleLabel.frame), CGRectGetMinY(self.titleLabel.frame));
}
break;
case JXCategoryDotRelativePosition_BottomLeft:
{
self.dotLayer.position = CGPointMake(CGRectGetMinX(self.titleLabel.frame), CGRectGetMaxY(self.titleLabel.frame));
}
break;
case JXCategoryDotRelativePosition_BottomRight:
{
self.dotLayer.position = CGPointMake(CGRectGetMaxX(self.titleLabel.frame), CGRectGetMaxY(self.titleLabel.frame));
}
break;
default:
break;
}
self.dotLayer.position = CGPointMake(CGRectGetMaxX(self.titleLabel.frame), CGRectGetMinY(self.titleLabel.frame));
[CATransaction commit];
}
- (void)reloadData:(JXCategoryBaseCellModel *)cellModel {
[super reloadData:cellModel];
JXCategoryDotCellModel *myCellModel = (JXCategoryDotCellModel *)cellModel;
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.dotLayer.hidden = !myCellModel.dotHidden;
self.dotLayer.backgroundColor = myCellModel.dotColor.CGColor;
self.dotLayer.cornerRadius = myCellModel.dotCornerRadius;
[CATransaction commit];
[self setNeedsLayout];
[self layoutIfNeeded];
}
@end
//
// JXCategoryDotCellModel.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/20.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryTitleCellModel.h"
typedef NS_ENUM(NSUInteger, JXCategoryDotRelativePosition) {
JXCategoryDotRelativePosition_TopLeft = 0,
JXCategoryDotRelativePosition_TopRight,
JXCategoryDotRelativePosition_BottomLeft,
JXCategoryDotRelativePosition_BottomRight,
};
@interface JXCategoryDotCellModel : JXCategoryTitleCellModel
@property (nonatomic, assign) BOOL dotHidden;
@property (nonatomic, assign) JXCategoryDotRelativePosition relativePosition;
@property (nonatomic, assign) CGSize dotSize;
@property (nonatomic, assign) CGFloat dotCornerRadius;
@property (nonatomic, strong) UIColor *dotColor;
@end
//
// JXCategoryDotCellModel.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/20.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryDotCellModel.h"
@implementation JXCategoryDotCellModel
@end
//
// JXCategoryDotView.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/20.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryTitleView.h"
#import "JXCategoryDotCell.h"
#import "JXCategoryDotCellModel.h"
@interface JXCategoryDotView : JXCategoryTitleView
@property (nonatomic, assign) JXCategoryDotRelativePosition relativePosition; //相对于titleLabel的位置,默认:JXCategoryDotRelativePosition_TopRight
@property (nonatomic, strong) NSArray <NSNumber *> *dotStates; //@(布尔值),控制红点是否显示
@property (nonatomic, assign) CGSize dotSize; //默认:CGSizeMake(10, 10)
@property (nonatomic, assign) CGFloat dotCornerRadius; //默认:JXCategoryViewAutomaticDimension(self.dotSize.height/2)
@property (nonatomic, strong) UIColor *dotColor; //默认:[UIColor redColor]
@end
//
// JXCategoryDotView.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/20.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryDotView.h"
@implementation JXCategoryDotView
- (void)initializeData {
[super initializeData];
_relativePosition = JXCategoryDotRelativePosition_TopRight;
_dotSize = CGSizeMake(10, 10);
_dotCornerRadius = JXCategoryViewAutomaticDimension;
_dotColor = [UIColor redColor];
}
- (Class)preferredCellClass {
return [JXCategoryDotCell class];
}
- (void)refreshDataSource {
NSMutableArray *tempArray = [NSMutableArray array];
for (int i = 0; i < self.titles.count; i++) {
JXCategoryDotCellModel *cellModel = [[JXCategoryDotCellModel alloc] init];
[tempArray addObject:cellModel];
}
self.dataSource = tempArray;
}
- (void)refreshCellModel:(JXCategoryBaseCellModel *)cellModel index:(NSInteger)index {
[super refreshCellModel:cellModel index:index];
JXCategoryDotCellModel *myCellModel = (JXCategoryDotCellModel *)cellModel;
myCellModel.dotHidden = [self.dotStates[index] boolValue];
myCellModel.relativePosition = self.relativePosition;
myCellModel.dotSize = self.dotSize;
myCellModel.dotColor = self.dotColor;
if (self.dotCornerRadius == JXCategoryViewAutomaticDimension) {
myCellModel.dotCornerRadius = self.dotSize.height/2;
}else {
myCellModel.dotCornerRadius = self.dotCornerRadius;
}
}
@end
//
// JXCategoryImageCell.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/20.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorCell.h"
@interface JXCategoryImageCell : JXCategoryIndicatorCell
@property (nonatomic, strong) UIImageView *imageView;
@end
//
// JXCategoryImageCell.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/20.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryImageCell.h"
#import "JXCategoryImageCellModel.h"
@implementation JXCategoryImageCell
- (void)initializeViews {
[super initializeViews];
_imageView = [[UIImageView alloc] init];
_imageView.contentMode = UIViewContentModeScaleAspectFit;
_imageView.layer.masksToBounds = YES;
[self.contentView addSubview:_imageView];
}
- (void)layoutSubviews {
[super layoutSubviews];
JXCategoryImageCellModel *myCellModel = (JXCategoryImageCellModel *)self.cellModel;
self.imageView.bounds = CGRectMake(0, 0, myCellModel.imageSize.width, myCellModel.imageSize.height);
self.imageView.center = self.contentView.center;
self.imageView.layer.cornerRadius = myCellModel.imageCornerRadius;
}
- (void)reloadData:(JXCategoryBaseCellModel *)cellModel {
[super reloadData:cellModel];
JXCategoryImageCellModel *myCellModel = (JXCategoryImageCellModel *)cellModel;
if (myCellModel.imageName != nil) {
self.imageView.image = [UIImage imageNamed:myCellModel.imageName];
}else if (myCellModel.imageURL != nil) {
if (myCellModel.loadImageCallback != nil) {
myCellModel.loadImageCallback(self.imageView, myCellModel.imageURL);
}
}
if (myCellModel.selected) {
if (myCellModel.selectedImageName != nil) {
self.imageView.image = [UIImage imageNamed:myCellModel.selectedImageName];
}else if (myCellModel.selectedImageURL != nil) {
if (myCellModel.loadImageCallback != nil) {
myCellModel.loadImageCallback(self.imageView, myCellModel.selectedImageURL);
}
}
}
if (myCellModel.imageZoomEnabled) {
self.imageView.transform = CGAffineTransformMakeScale(myCellModel.imageZoomScale, myCellModel.imageZoomScale);
}else {
self.imageView.transform = CGAffineTransformIdentity;
}
}
@end
//
// JXCategoryImageCellModel.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/20.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorCellModel.h"
@interface JXCategoryImageCellModel : JXCategoryIndicatorCellModel
@property (nonatomic, copy) void(^loadImageCallback)(UIImageView *imageView, NSURL *imageURL);
@property (nonatomic, copy) NSString *imageName; //加载bundle内的图片
@property (nonatomic, strong) NSURL *imageURL; //图片URL
@property (nonatomic, copy) NSString *selectedImageName;
@property (nonatomic, strong) NSURL *selectedImageURL;
@property (nonatomic, assign) CGSize imageSize;
@property (nonatomic, assign) CGFloat imageCornerRadius;
@property (nonatomic, assign) BOOL imageZoomEnabled;
@property (nonatomic, assign) CGFloat imageZoomScale;
@end
//
// JXCategoryImageCellModel.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/20.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryImageCellModel.h"
@implementation JXCategoryImageCellModel
@end
//
// JXCategoryImageView.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/20.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorView.h"
#import "JXCategoryImageCell.h"
#import "JXCategoryImageCellModel.h"
@interface JXCategoryImageView : JXCategoryIndicatorView
@property (nonatomic, strong) NSArray <NSString *>*imageNames;
@property (nonatomic, strong) NSArray <NSURL *>*imageURLs;
@property (nonatomic, strong) NSArray <NSString *>*selectedImageNames;
@property (nonatomic, strong) NSArray <NSURL *>*selectedImageURLs;
@property (nonatomic, copy) void(^loadImageCallback)(UIImageView *imageView, NSURL *imageURL); //使用imageURL从远端下载图片进行加载,建议使用SDWebImage等第三方库进行下载。
@property (nonatomic, assign) CGSize imageSize; //默认CGSizeMake(20, 20)
@property (nonatomic, assign) CGFloat imageCornerRadius; //图片圆角
@property (nonatomic, assign) BOOL imageZoomEnabled; //默认为NO
@property (nonatomic, assign) CGFloat imageZoomScale; //默认1.2,imageZoomEnabled为YES才生效
@end
//
// JXCategoryImageView.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/20.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryImageView.h"
#import "JXCategoryFactory.h"
@implementation JXCategoryImageView
- (void)dealloc
{
self.loadImageCallback = nil;
}
- (void)initializeData {
[super initializeData];
_imageSize = CGSizeMake(20, 20);
_imageZoomEnabled = NO;
_imageZoomScale = 1.2;
_imageCornerRadius = 0;
}
- (Class)preferredCellClass {
return [JXCategoryImageCell class];
}
- (void)refreshDataSource {
NSMutableArray *tempArray = [NSMutableArray array];
NSUInteger count = (self.imageNames.count > 0) ? self.imageNames.count : (self.imageURLs.count > 0 ? self.imageURLs.count : 0);
for (int i = 0; i < count; i++) {
JXCategoryImageCellModel *cellModel = [[JXCategoryImageCellModel alloc] init];
[tempArray addObject:cellModel];
}
self.dataSource = tempArray;
}
- (void)refreshSelectedCellModel:(JXCategoryBaseCellModel *)selectedCellModel unselectedCellModel:(JXCategoryBaseCellModel *)unselectedCellModel {
[super refreshSelectedCellModel:selectedCellModel unselectedCellModel:unselectedCellModel];
JXCategoryImageCellModel *myUnselectedCellModel = (JXCategoryImageCellModel *)unselectedCellModel;
myUnselectedCellModel.imageZoomScale = 1.0;
JXCategoryImageCellModel *myselectedCellModel = (JXCategoryImageCellModel *)selectedCellModel;
myselectedCellModel.imageZoomScale = self.imageZoomScale;
}
- (void)refreshCellModel:(JXCategoryBaseCellModel *)cellModel index:(NSInteger)index {
[super refreshCellModel:cellModel index:index];
JXCategoryImageCellModel *myCellModel = (JXCategoryImageCellModel *)cellModel;
myCellModel.loadImageCallback = self.loadImageCallback;
myCellModel.imageSize = self.imageSize;
myCellModel.imageCornerRadius = self.imageCornerRadius;
if (self.imageNames != nil) {
myCellModel.imageName = self.imageNames[index];
}else if (self.imageURLs != nil) {
myCellModel.imageURL = self.imageURLs[index];
}
if (self.selectedImageNames != nil) {
myCellModel.selectedImageName = self.selectedImageNames[index];
}else if (self.selectedImageURLs != nil) {
myCellModel.selectedImageURL = self.selectedImageURLs[index];
}
myCellModel.imageZoomEnabled = self.imageZoomEnabled;
myCellModel.imageZoomScale = 1.0;
if (index == self.selectedIndex) {
myCellModel.imageZoomScale = self.imageZoomScale;
}
}
- (void)refreshLeftCellModel:(JXCategoryBaseCellModel *)leftCellModel rightCellModel:(JXCategoryBaseCellModel *)rightCellModel ratio:(CGFloat)ratio {
[super refreshLeftCellModel:leftCellModel rightCellModel:rightCellModel ratio:ratio];
JXCategoryImageCellModel *leftModel = (JXCategoryImageCellModel *)leftCellModel;
JXCategoryImageCellModel *rightModel = (JXCategoryImageCellModel *)rightCellModel;
if (self.imageZoomEnabled) {
leftModel.imageZoomScale = [JXCategoryFactory interpolationFrom:self.imageZoomScale to:1.0 percent:ratio];
rightModel.imageZoomScale = [JXCategoryFactory interpolationFrom:1.0 to:self.imageZoomScale percent:ratio];
}
}
- (CGFloat)preferredCellWidthAtIndex:(NSInteger)index {
return self.imageSize.width;
}
@end
//
// JXCategoryIndicatorBackgroundView.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorComponentView.h"
@interface JXCategoryIndicatorBackgroundView : JXCategoryIndicatorComponentView
@property (nonatomic, assign) CGFloat backgroundViewWidth; //默认JXCategoryViewAutomaticDimension(与cellWidth相等)
@property (nonatomic, assign) CGFloat backgroundViewWidthIncrement; //宽度增量补偿,因为backgroundEllipseLayer一般会比实际内容大一些。默认10
@property (nonatomic, assign) CGFloat backgroundViewHeight; //默认JXCategoryViewAutomaticDimension(与cell高度相等)
@property (nonatomic, assign) CGFloat backgroundViewCornerRadius; //默认JXCategoryViewAutomaticDimension(即backgroundViewHeight/2)
@property (nonatomic, strong) UIColor *backgroundViewColor; //默认为[UIColor redColor]
@end
//
// JXCategoryIndicatorBackgroundView.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorBackgroundView.h"
#import "JXCategoryFactory.h"
@implementation JXCategoryIndicatorBackgroundView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_backgroundViewWidth = JXCategoryViewAutomaticDimension;
_backgroundViewHeight = JXCategoryViewAutomaticDimension;
_backgroundViewCornerRadius = JXCategoryViewAutomaticDimension;
_backgroundViewColor = [UIColor lightGrayColor];
_backgroundViewWidthIncrement = 10;
}
return self;
}
#pragma mark - JXCategoryIndicatorProtocol
- (void)jx_refreshState:(JXCategoryIndicatorParamsModel *)model {
self.layer.cornerRadius = [self getBackgroundViewCornerRadius:model.selectedCellFrame];
self.backgroundColor = self.backgroundViewColor;
CGFloat width = [self getBackgroundViewWidth:model.selectedCellFrame];
CGFloat height = [self getBackgroundViewHeight:model.selectedCellFrame];
CGFloat x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - width)/2;
CGFloat y = (model.selectedCellFrame.size.height - height)/2;
self.frame = CGRectMake(x, y, width, height);
}
- (void)jx_contentScrollViewDidScroll:(JXCategoryIndicatorParamsModel *)model {
CGRect rightCellFrame = model.rightCellFrame;
CGRect leftCellFrame = model.leftCellFrame;
CGFloat percent = model.percent;
CGFloat targetX = 0;
CGFloat targetWidth = [self getBackgroundViewWidth:leftCellFrame];
if (percent == 0) {
targetX = leftCellFrame.origin.x + (leftCellFrame.size.width - targetWidth)/2.0;
}else {
CGFloat leftWidth = targetWidth;
CGFloat rightWidth = [self getBackgroundViewWidth:rightCellFrame];
CGFloat leftX = leftCellFrame.origin.x + (leftCellFrame.size.width - leftWidth)/2;
CGFloat rightX = rightCellFrame.origin.x + (rightCellFrame.size.width - rightWidth)/2;
targetX = [JXCategoryFactory interpolationFrom:leftX to:rightX percent:percent];
if (self.backgroundViewWidth == JXCategoryViewAutomaticDimension) {
targetWidth = [JXCategoryFactory interpolationFrom:leftWidth to:rightWidth percent:percent];
}
}
//允许变动frame的情况:1、允许滚动;2、不允许滚动,但是已经通过手势滚动切换一页内容了;
if (self.scrollEnabled == YES || (self.scrollEnabled == NO && percent == 0)) {
CGFloat height = [self getBackgroundViewHeight:leftCellFrame];
CGFloat y = (leftCellFrame.size.height - height)/2;
self.frame = CGRectMake(targetX, y, targetWidth, height);
}
}
- (void)jx_selectedCell:(JXCategoryIndicatorParamsModel *)model {
CGFloat width = [self getBackgroundViewWidth:model.selectedCellFrame];
CGFloat height = [self getBackgroundViewHeight:model.selectedCellFrame];
CGFloat x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - width)/2;
CGFloat y = (model.selectedCellFrame.size.height - height)/2;
CGRect toFrame = CGRectMake(x, y, width, height);
if (self.scrollEnabled) {
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
self.frame = toFrame;
} completion:^(BOOL finished) {
}];
}else {
self.frame = toFrame;
}
}
#pragma mark - Private
- (CGFloat)getBackgroundViewWidth:(CGRect)cellFrame
{
if (self.backgroundViewWidth == JXCategoryViewAutomaticDimension) {
return cellFrame.size.width + self.backgroundViewWidthIncrement;
}
return self.backgroundViewWidth + self.backgroundViewWidthIncrement;
}
- (CGFloat)getBackgroundViewHeight:(CGRect)cellFrame
{
if (self.backgroundViewHeight == JXCategoryViewAutomaticDimension) {
return cellFrame.size.height;
}
return self.backgroundViewHeight;
}
- (CGFloat)getBackgroundViewCornerRadius:(CGRect)cellFrame {
if (self.backgroundViewCornerRadius == JXCategoryViewAutomaticDimension) {
return [self getBackgroundViewHeight:cellFrame]/2;
}
return self.backgroundViewCornerRadius;
}
@end
//
// JXCategoryIndicatorBallView.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/21.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorComponentView.h"
@interface JXCategoryIndicatorBallView : JXCategoryIndicatorComponentView
@property (nonatomic, assign) CGSize ballViewSize; //默认:CGSizeMake(15, 15)
@property (nonatomic, assign) CGFloat ballScrollOffsetX; //小红点的偏移量 默认:20
@property (nonatomic, strong) UIColor *ballViewColor; //默认为[UIColor redColor]
@end
//
// JXCategoryIndicatorBallView.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/21.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorBallView.h"
#import "JXCategoryFactory.h"
@interface JXCategoryIndicatorBallView ()
@property (nonatomic, strong) UIView *smallBall;
@property (nonatomic, strong) UIView *bigBall;
@property (nonatomic, strong) CAShapeLayer *shapeLayer;
@end
@implementation JXCategoryIndicatorBallView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_ballViewSize = CGSizeMake(15, 15);
_ballScrollOffsetX = 20;
_ballViewColor = [UIColor redColor];
_smallBall = [[UIView alloc] init];
[self addSubview:self.smallBall];
_bigBall = [[UIView alloc] init];
[self addSubview:self.bigBall];
_shapeLayer = [CAShapeLayer layer];
[self.layer addSublayer:self.shapeLayer];
}
return self;
}
#pragma mark - JXCategoryIndicatorProtocol
- (void)jx_refreshState:(JXCategoryIndicatorParamsModel *)model {
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.shapeLayer.fillColor = self.ballViewColor.CGColor;
[CATransaction commit];
self.smallBall.backgroundColor = self.ballViewColor;
self.smallBall.layer.cornerRadius = self.ballViewSize.height/2;
self.bigBall.backgroundColor = self.ballViewColor;
self.bigBall.layer.cornerRadius = self.ballViewSize.height/2;
CGFloat x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - self.ballViewSize.width)/2;
CGFloat y = self.superview.bounds.size.height - self.ballViewSize.height - self.verticalMargin;
if (self.componentPosition == JXCategoryComponentPosition_Top) {
y = self.verticalMargin;
}
self.smallBall.frame = CGRectMake(x, y, self.ballViewSize.width, self.ballViewSize.height);
self.bigBall.frame = CGRectMake(x, y, self.ballViewSize.width, self.ballViewSize.height);
}
- (void)jx_contentScrollViewDidScroll:(JXCategoryIndicatorParamsModel *)model {
CGRect rightCellFrame = model.rightCellFrame;
CGRect leftCellFrame = model.leftCellFrame;
CGFloat percent = model.percent;
CGFloat targetXOfBigBall = 0;
CGFloat targetXOfSmallBall = leftCellFrame.origin.x + (leftCellFrame.size.width - self.ballViewSize.width)/2;
CGFloat targetWidthOfSmallBall = self.ballViewSize.width;
if (percent == 0) {
targetXOfBigBall = leftCellFrame.origin.x + (leftCellFrame.size.width - self.ballViewSize.width)/2.0;
targetXOfSmallBall = leftCellFrame.origin.x + (leftCellFrame.size.width - targetWidthOfSmallBall)/2.0;
}else {
CGFloat leftX = leftCellFrame.origin.x + (leftCellFrame.size.width - self.ballViewSize.width)/2;
CGFloat rightX = rightCellFrame.origin.x + (rightCellFrame.size.width - self.ballViewSize.width)/2;
//前50%,移动bigBall的x,缩小smallBall;后50%,移动bigBall的x,缩小smallBall,移动smallBall的x
if (percent <= 0.5) {
targetXOfBigBall = [JXCategoryFactory interpolationFrom:leftX to:(rightX - self.ballScrollOffsetX) percent:percent*2];
targetWidthOfSmallBall = [JXCategoryFactory interpolationFrom:self.ballViewSize.width to:self.ballViewSize.width/2 percent:percent*2];
}else {
targetXOfBigBall = [JXCategoryFactory interpolationFrom:(rightX - self.ballScrollOffsetX) to:rightX percent:(percent - 0.5)*2];
targetWidthOfSmallBall = [JXCategoryFactory interpolationFrom:self.ballViewSize.width/2 to:0 percent:(percent - 0.5)*2];
targetXOfSmallBall = [JXCategoryFactory interpolationFrom:leftX to:rightX percent:(percent - 0.5)*2];
}
}
//允许变动frame的情况:1、允许滚动;2、不允许滚动,但是已经通过手势滚动切换一页内容了;
if (self.scrollEnabled == YES || (self.scrollEnabled == NO && percent == 0)) {
CGRect bigBallFrame = self.bigBall.frame;
bigBallFrame.origin.x = targetXOfBigBall;
self.bigBall.frame = bigBallFrame;
self.bigBall.layer.cornerRadius = bigBallFrame.size.height/2;
CGFloat targetYOfSmallBall = self.superview.bounds.size.height - self.ballViewSize.height/2 - targetWidthOfSmallBall/2 - self.verticalMargin;
if (self.componentPosition == JXCategoryComponentPosition_Top) {
targetYOfSmallBall = self.ballViewSize.height/2 - targetWidthOfSmallBall/2 + self.verticalMargin;
}
self.smallBall.frame = CGRectMake(targetXOfSmallBall, targetYOfSmallBall, targetWidthOfSmallBall, targetWidthOfSmallBall);
self.smallBall.layer.cornerRadius = targetWidthOfSmallBall/2;
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.shapeLayer.path = [self getBezierPathWithSmallCir:self.smallBall andBigCir:self.bigBall].CGPath;
[CATransaction commit];
}
}
- (void)jx_selectedCell:(JXCategoryIndicatorParamsModel *)model {
CGFloat x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - self.ballViewSize.width)/2;
CGFloat y = self.superview.bounds.size.height - self.ballViewSize.height - self.verticalMargin;
if (self.componentPosition == JXCategoryComponentPosition_Top) {
y = self.verticalMargin;
}
CGRect toFrame = CGRectMake(x, y, self.ballViewSize.width, self.ballViewSize.height);
if (self.scrollEnabled) {
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.smallBall.frame = toFrame;
self.bigBall.frame = toFrame;
self.smallBall.layer.cornerRadius = self.ballViewSize.height/2;
self.bigBall.layer.cornerRadius = self.ballViewSize.height/2;
} completion:^(BOOL finished) {
}];
}else {
self.smallBall.frame = toFrame;
self.bigBall.frame = toFrame;
self.smallBall.layer.cornerRadius = self.ballViewSize.height/2;
self.bigBall.layer.cornerRadius = self.ballViewSize.height/2;
}
}
- (UIBezierPath *)getBezierPathWithSmallCir:(UIView *)smallCir andBigCir:(UIView *)bigCir{
// 获取最小的圆
if (bigCir.frame.size.width < smallCir.frame.size.width) {
UIView *view = bigCir;
bigCir = smallCir;
smallCir = view;
}
// 获取小圆的信息
CGFloat d = self.bigBall.center.x - self.smallBall.center.x;
if (d == 0) {
return nil;
}
CGFloat x1 = smallCir.center.x;
CGFloat y1 = smallCir.center.y;
CGFloat r1 = smallCir.bounds.size.width/2;
// 获取大圆的信息
CGFloat x2 = bigCir.center.x;
CGFloat y2 = bigCir.center.y;
CGFloat r2 = bigCir.bounds.size.width/2;
// 获取三角函数
CGFloat sinA = (y2 - y1)/d;
CGFloat cosA = (x2 - x1)/d;
// 获取矩形四个点
CGPoint pointA = CGPointMake(x1 - sinA*r1, y1 + cosA * r1);
CGPoint pointB = CGPointMake(x1 + sinA*r1, y1 - cosA * r1);
CGPoint pointC = CGPointMake(x2 + sinA*r2, y2 - cosA * r2);
CGPoint pointD = CGPointMake(x2 - sinA*r2, y2 + cosA * r2);
// 获取控制点,以便画出曲线
CGPoint pointO = CGPointMake(pointA.x + d / 2 * cosA , pointA.y + d / 2 * sinA);
CGPoint pointP = CGPointMake(pointB.x + d / 2 * cosA , pointB.y + d / 2 * sinA);
// 创建路径
UIBezierPath *path =[UIBezierPath bezierPath];
[path moveToPoint:pointA];
[path addLineToPoint:pointB];
[path addQuadCurveToPoint:pointC controlPoint:pointP];
[path addLineToPoint:pointD];
[path addQuadCurveToPoint:pointA controlPoint:pointO];
return path;
}
@end
//
// JXCategoryComponentBaseView.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "JXCategoryIndicatorProtocol.h"
#import "JXCategoryViewDefines.h"
@interface JXCategoryIndicatorComponentView : UIView <JXCategoryIndicatorProtocol>
/**
指示器的位置。底部或者顶部
*/
@property (nonatomic, assign) JXCategoryComponentPosition componentPosition;
/**
默认JXCategoryViewAutomaticDimension(与cell的宽度相等)。内部通过`- (CGFloat)indicatorWidthValue:(CGRect)cellFrame`方法获取实际的值
*/
@property (nonatomic, assign) CGFloat indicatorWidth;
/**
指示器的宽度增量。比如需求是指示器宽度比cell宽度多10 point。就可以将该属性赋值为10。最终指示器的宽度=indicatorWidth+indicatorWidthIncrement
*/
@property (nonatomic, assign) CGFloat indicatorWidthIncrement;
/**
默认:3。内部通过`- (CGFloat)indicatorHeightValue:(CGRect)cellFrame`方法获取实际的值
*/
@property (nonatomic, assign) CGFloat indicatorHeight;
/**
默认JXCategoryViewAutomaticDimension (等于indicatorHeight/2)。内部通过`- (CGFloat)indicatorCornerRadiusValue:(CGRect)cellFrame`方法获取实际的值
*/
@property (nonatomic, assign) CGFloat indicatorCornerRadius;
/**
指示器的颜色
*/
@property (nonatomic, strong) UIColor *indicatorColor;
/**
垂直方向偏移。数值越大越靠近中心。默认:0。
*/
@property (nonatomic, assign) CGFloat verticalMargin;
/**
手势滚动、点击切换的时候,是否允许滚动,默认YES
*/
@property (nonatomic, assign, getter=isScrollEnabled) BOOL scrollEnabled;
/**
手势滚动、点击切换的时候,如果允许滚动,分为简单滚动和复杂滚动。默认为:JXCategoryIndicatorScrollStyleSimple
目前仅JXCategoryIndicatorLineView、JXCategoryIndicatorDotLineView支持,其他子类暂不支持。
*/
@property (nonatomic, assign) JXCategoryIndicatorScrollStyle scrollStyle;
/**
滚动动画的时间。默认0.25
*/
@property (nonatomic, assign) NSTimeInterval scrollAnimationDuration;
/**
传入cellFrame获取指示器的最终宽度
@param cellFrame cellFrame
@return 指示器的最终宽度
*/
- (CGFloat)indicatorWidthValue:(CGRect)cellFrame;
/**
传入cellFrame获取指示器的最终高度
@param cellFrame cellFrame
@return 指示器的最终高度
*/
- (CGFloat)indicatorHeightValue:(CGRect)cellFrame;
/**
传入cellFrame获取指示器的最终圆角
@param cellFrame cellFrame
@return 指示器的最终圆角
*/
- (CGFloat)indicatorCornerRadiusValue:(CGRect)cellFrame;
@end
//
// JXCategoryComponentBaseView.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorComponentView.h"
@implementation JXCategoryIndicatorComponentView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_componentPosition = JXCategoryComponentPosition_Bottom;
_scrollEnabled = YES;
_verticalMargin = 0;
_scrollAnimationDuration = 0.25;
_indicatorWidth = JXCategoryViewAutomaticDimension;
_indicatorWidthIncrement = 0;
_indicatorHeight = 3;
_indicatorCornerRadius = JXCategoryViewAutomaticDimension;
_indicatorColor = [UIColor redColor];
_scrollStyle = JXCategoryIndicatorScrollStyleSimple;
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
NSAssert(NO, @"Use initWithFrame");
}
return self;
}
- (CGFloat)indicatorWidthValue:(CGRect)cellFrame {
if (self.indicatorWidth == JXCategoryViewAutomaticDimension) {
return cellFrame.size.width + self.indicatorWidthIncrement;
}
return self.indicatorWidth + self.indicatorWidthIncrement;
}
- (CGFloat)indicatorHeightValue:(CGRect)cellFrame {
if (self.indicatorHeight == JXCategoryViewAutomaticDimension) {
return cellFrame.size.height;
}
return self.indicatorHeight;
}
- (CGFloat)indicatorCornerRadiusValue:(CGRect)cellFrame {
if (self.indicatorCornerRadius == JXCategoryViewAutomaticDimension) {
return [self indicatorHeightValue:cellFrame]/2;
}
return self.indicatorCornerRadius;
}
#pragma mark - JXCategoryIndicatorProtocol
- (void)jx_refreshState:(JXCategoryIndicatorParamsModel *)model {
}
- (void)jx_contentScrollViewDidScroll:(JXCategoryIndicatorParamsModel *)model {
}
- (void)jx_selectedCell:(JXCategoryIndicatorParamsModel *)model {
}
@end
//
// JXCategoryIndicatorImageView.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorComponentView.h"
@interface JXCategoryIndicatorImageView : JXCategoryIndicatorComponentView
@property (nonatomic, strong, readonly) UIImageView *indicatorImageView;
@property (nonatomic, assign) BOOL indicatorImageViewRollEnabled; //默认NO
@property (nonatomic, assign) CGSize indicatorImageViewSize; //默认:CGSizeMake(30, 20)
@end
//
// JXCategoryIndicatorImageView.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorImageView.h"
#import "JXCategoryFactory.h"
@implementation JXCategoryIndicatorImageView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_indicatorImageViewSize = CGSizeMake(30, 20);
_indicatorImageViewRollEnabled = NO;
_indicatorImageView = [[UIImageView alloc] init];
self.indicatorImageView.frame = CGRectMake(0, 0, self.indicatorImageViewSize.width, self.indicatorImageViewSize.height);
self.indicatorImageView.contentMode = UIViewContentModeScaleAspectFit;
[self addSubview:self.indicatorImageView];
}
return self;
}
- (void)setIndicatorImageViewSize:(CGSize)indicatorImageViewSize {
_indicatorImageViewSize = indicatorImageViewSize;
self.indicatorImageView.frame = CGRectMake(0, 0, self.indicatorImageViewSize.width, self.indicatorImageViewSize.height);
}
#pragma mark - JXCategoryIndicatorProtocol
- (void)jx_refreshState:(JXCategoryIndicatorParamsModel *)model {
CGFloat x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - self.indicatorImageViewSize.width)/2;
CGFloat y = self.superview.bounds.size.height - self.indicatorImageViewSize.height - self.verticalMargin;
if (self.componentPosition == JXCategoryComponentPosition_Top) {
y = self.verticalMargin;
}
self.frame = CGRectMake(x, y, self.indicatorImageViewSize.width, self.indicatorImageViewSize.height);
}
- (void)jx_contentScrollViewDidScroll:(JXCategoryIndicatorParamsModel *)model {
CGRect rightCellFrame = model.rightCellFrame;
CGRect leftCellFrame = model.leftCellFrame;
CGFloat percent = model.percent;
CGFloat targetWidth = self.indicatorImageViewSize.width;
CGFloat targetX = 0;
if (percent == 0) {
targetX = leftCellFrame.origin.x + (leftCellFrame.size.width - targetWidth)/2.0;
}else {
CGFloat leftX = leftCellFrame.origin.x + (leftCellFrame.size.width - targetWidth)/2;
CGFloat rightX = rightCellFrame.origin.x + (rightCellFrame.size.width - targetWidth)/2;
targetX = [JXCategoryFactory interpolationFrom:leftX to:rightX percent:percent];
}
//允许变动frame的情况:1、允许滚动;2、不允许滚动,但是已经通过手势滚动切换一页内容了;
if (self.scrollEnabled == YES || (self.scrollEnabled == NO && percent == 0)) {
CGRect frame = self.frame;
frame.origin.x = targetX;
self.frame = frame;
if (self.indicatorImageViewRollEnabled) {
self.indicatorImageView.transform = CGAffineTransformMakeRotation(M_PI*2*percent);
}
}
}
- (void)jx_selectedCell:(JXCategoryIndicatorParamsModel *)model {
CGRect toFrame = self.frame;
toFrame.origin.x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - self.indicatorImageViewSize.width)/2;
if (self.scrollEnabled) {
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
self.frame = toFrame;
} completion:^(BOOL finished) {
}];
if (self.indicatorImageViewRollEnabled && model.isClicked) {
[self.indicatorImageView.layer removeAnimationForKey:@"rotate"];
CABasicAnimation *rotateAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
if (model.selectedIndex > model.lastSelectedIndex) {
rotateAnimation.fromValue = @(0);
rotateAnimation.toValue = @(M_PI*2);
}else {
rotateAnimation.fromValue = @(M_PI*2);
rotateAnimation.toValue = @(0);
}
rotateAnimation.fillMode = kCAFillModeBackwards;
rotateAnimation.removedOnCompletion = YES;
rotateAnimation.duration = 0.25;
[self.indicatorImageView.layer addAnimation:rotateAnimation forKey:@"rotate"];
}
}else {
self.frame = toFrame;
}
}
@end
//
// JXCategoryIndicatorLineView.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorComponentView.h"
typedef NS_ENUM(NSUInteger, JXCategoryIndicatorLineStyle) {
JXCategoryIndicatorLineStyle_Normal = 0,
JXCategoryIndicatorLineStyle_Lengthen = 1,
JXCategoryIndicatorLineStyle_LengthenOffset = 2,
};
@interface JXCategoryIndicatorLineView : JXCategoryIndicatorComponentView
@property (nonatomic, assign) JXCategoryIndicatorLineStyle lineStyle;
/**
line滚动时x的偏移量,默认为10;
lineStyle为JXCategoryIndicatorLineStyle_LengthenOffset有用;
*/
@property (nonatomic, assign) CGFloat lineScrollOffsetX;
@end
//
// JXCategoryIndicatorLineView.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorLineView.h"
#import "JXCategoryFactory.h"
#import "JXCategoryViewDefines.h"
#import "JXCategoryViewAnimator.h"
@interface JXCategoryIndicatorLineView ()
@property (nonatomic, strong) JXCategoryViewAnimator *animator;
@end
@implementation JXCategoryIndicatorLineView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_lineStyle = JXCategoryIndicatorLineStyle_Normal;
_lineScrollOffsetX = 10;
self.indicatorHeight = 3;
}
return self;
}
#pragma mark - JXCategoryIndicatorProtocol
- (void)jx_refreshState:(JXCategoryIndicatorParamsModel *)model {
self.backgroundColor = self.indicatorColor;
self.layer.cornerRadius = [self indicatorCornerRadiusValue:model.selectedCellFrame];
CGFloat selectedLineWidth = [self indicatorWidthValue:model.selectedCellFrame];
CGFloat x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - selectedLineWidth)/2;
CGFloat y = self.superview.bounds.size.height - [self indicatorHeightValue:model.selectedCellFrame] - self.verticalMargin;
if (self.componentPosition == JXCategoryComponentPosition_Top) {
y = self.verticalMargin;
}
self.frame = CGRectMake(x, y, selectedLineWidth, [self indicatorHeightValue:model.selectedCellFrame]);
}
- (void)jx_contentScrollViewDidScroll:(JXCategoryIndicatorParamsModel *)model {
if (self.animator.isExecuting) {
[self.animator invalid];
self.animator = nil;
}
CGRect rightCellFrame = model.rightCellFrame;
CGRect leftCellFrame = model.leftCellFrame;
CGFloat percent = model.percent;
CGFloat targetX = leftCellFrame.origin.x;
CGFloat targetWidth = [self indicatorWidthValue:leftCellFrame];
CGFloat leftWidth = targetWidth;
CGFloat rightWidth = [self indicatorWidthValue:rightCellFrame];
CGFloat leftX = leftCellFrame.origin.x + (leftCellFrame.size.width - leftWidth)/2;
CGFloat rightX = rightCellFrame.origin.x + (rightCellFrame.size.width - rightWidth)/2;
if (self.lineStyle == JXCategoryIndicatorLineStyle_Normal) {
targetX = [JXCategoryFactory interpolationFrom:leftX to:rightX percent:0];
NSLog(@"targetX--%.2f", targetX);
if (self.indicatorWidth == JXCategoryViewAutomaticDimension) {
targetWidth = [JXCategoryFactory interpolationFrom:leftWidth to:rightWidth percent:percent];
NSLog(@"targetWidth--%.2f", targetX);
}
}else if (self.lineStyle == JXCategoryIndicatorLineStyle_Lengthen) {
CGFloat maxWidth = rightX - leftX + rightWidth;
//前50%,只增加width;后50%,移动x并减小width
if (percent <= 0.5) {
targetX = leftX;
targetWidth = [JXCategoryFactory interpolationFrom:leftWidth to:maxWidth percent:percent*2];
}else {
targetX = [JXCategoryFactory interpolationFrom:leftX to:rightX percent:(percent - 0.5)*2];
NSLog(@"targetX--%.2f", targetX);
targetWidth = [JXCategoryFactory interpolationFrom:maxWidth to:rightWidth percent:(percent - 0.5)*2];
NSLog(@"targetWidth--%.2f", targetWidth);
}
}else if (self.lineStyle == JXCategoryIndicatorLineStyle_LengthenOffset) {
//前50%,增加width,并少量移动x;后50%,少量移动x并减小width
CGFloat offsetX = self.lineScrollOffsetX;//x的少量偏移量
CGFloat maxWidth = rightX - leftX + rightWidth - offsetX*2;
if (percent <= 0.5) {
targetX = [JXCategoryFactory interpolationFrom:leftX to:leftX + offsetX percent:percent*2];;
targetWidth = [JXCategoryFactory interpolationFrom:leftWidth to:maxWidth percent:percent*2];
}else {
targetX = [JXCategoryFactory interpolationFrom:(leftX + offsetX) to:rightX percent:(percent - 0.5)*2];
targetWidth = [JXCategoryFactory interpolationFrom:maxWidth to:rightWidth percent:(percent - 0.5)*2];
}
}
//允许变动frame的情况:1、允许滚动;2、不允许滚动,但是已经通过手势滚动切换一页内容了;
if (self.isScrollEnabled == YES || (self.isScrollEnabled == NO && percent == 0)) {
CGRect frame = self.frame;
frame.origin.x = targetX;
frame.size.width = targetWidth;
self.frame = frame;
}
}
- (void)jx_selectedCell:(JXCategoryIndicatorParamsModel *)model {
CGRect targetIndicatorFrame = self.frame;
CGFloat targetIndicatorWidth = [self indicatorWidthValue:model.selectedCellFrame];
targetIndicatorFrame.origin.x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - targetIndicatorWidth)/2.0;
targetIndicatorFrame.size.width = targetIndicatorWidth;
if (self.isScrollEnabled) {
if (self.scrollStyle == JXCategoryIndicatorScrollStyleSameAsUserScroll) {
if (self.animator.isExecuting) {
[self.animator invalid];
self.animator = nil;
}
CGFloat leftX = 0;
CGFloat rightX = 0;
CGFloat leftWidth = 0;
CGFloat rightWidth = 0;
BOOL isNeedReversePercent = NO;
if (self.frame.origin.x > model.selectedCellFrame.origin.x) {
leftWidth = [self indicatorWidthValue:model.selectedCellFrame];
rightWidth = self.frame.size.width;
leftX = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - leftWidth)/2;;
rightX = self.frame.origin.x;
isNeedReversePercent = YES;
}else {
leftWidth = self.frame.size.width;
rightWidth = [self indicatorWidthValue:model.selectedCellFrame];
leftX = self.frame.origin.x;
rightX = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - rightWidth)/2;
}
__weak typeof(self) weakSelf = self;
if (self.lineStyle == JXCategoryIndicatorLineStyle_Normal) {
[UIView animateWithDuration:self.scrollAnimationDuration delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.frame = targetIndicatorFrame;
} completion: nil];
}else if (self.lineStyle == JXCategoryIndicatorLineStyle_Lengthen) {
CGFloat maxWidth = rightX - leftX + rightWidth;
//前50%,只增加width;后50%,移动x并减小width
self.animator = [[JXCategoryViewAnimator alloc] init];
self.animator.progressCallback = ^(CGFloat percent) {
if (isNeedReversePercent) {
percent = 1 - percent;
}
CGFloat targetX = 0;
CGFloat targetWidth = 0;
if (percent <= 0.5) {
targetX = leftX;
targetWidth = [JXCategoryFactory interpolationFrom:leftWidth to:maxWidth percent:percent*2];
}else {
targetX = [JXCategoryFactory interpolationFrom:leftX to:rightX percent:(percent - 0.5)*2];
targetWidth = [JXCategoryFactory interpolationFrom:maxWidth to:rightWidth percent:(percent - 0.5)*2];
}
CGRect toFrame = weakSelf.frame;
toFrame.origin.x = targetX;
toFrame.size.width = targetWidth;
weakSelf.frame = toFrame;
};
[self.animator start];
}else if (self.lineStyle == JXCategoryIndicatorLineStyle_LengthenOffset) {
//前50%,增加width,并少量移动x;后50%,少量移动x并减小width
CGFloat offsetX = self.lineScrollOffsetX;//x的少量偏移量
CGFloat maxWidth = rightX - leftX + rightWidth - offsetX*2;
self.animator = [[JXCategoryViewAnimator alloc] init];
self.animator.progressCallback = ^(CGFloat percent) {
if (isNeedReversePercent) {
percent = 1 - percent;
}
CGFloat targetX = 0;
CGFloat targetWidth = 0;
if (percent <= 0.5) {
targetX = [JXCategoryFactory interpolationFrom:leftX to:leftX + offsetX percent:percent*2];;
targetWidth = [JXCategoryFactory interpolationFrom:leftWidth to:maxWidth percent:percent*2];
}else {
targetX = [JXCategoryFactory interpolationFrom:(leftX + offsetX) to:rightX percent:(percent - 0.5)*2];
targetWidth = [JXCategoryFactory interpolationFrom:maxWidth to:rightWidth percent:(percent - 0.5)*2];
}
CGRect toFrame = weakSelf.frame;
toFrame.origin.x = targetX;
toFrame.size.width = targetWidth;
weakSelf.frame = toFrame;
};
[self.animator start];
}
}else if (self.scrollStyle == JXCategoryIndicatorScrollStyleSimple) {
[UIView animateWithDuration:self.scrollAnimationDuration delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.frame = targetIndicatorFrame;
} completion: nil];
}
}else {
self.frame = targetIndicatorFrame;
}
}
@end
//
// JXCategoryIndicatorRainbowLineView.h
// JXCategoryView
//
// Created by jiaxin on 2018/12/13.
// Copyright © 2018 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorLineView.h"
NS_ASSUME_NONNULL_BEGIN
/**
会无视JXCategoryIndicatorLineView的indicatorLineViewColor属性,以indicatorColors为准
*/
@interface JXCategoryIndicatorRainbowLineView : JXCategoryIndicatorLineView
@property (nonatomic, strong) NSArray <UIColor *> *indicatorColors; //数量需要与cell的数量相等。没有提供默认值,必须要赋值该属性。categoryView在reloadData的时候,也要一并更新该属性,不然会出现数组越界。
@end
NS_ASSUME_NONNULL_END
//
// JXCategoryIndicatorRainbowLineView.m
// JXCategoryView
//
// Created by jiaxin on 2018/12/13.
// Copyright © 2018 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorRainbowLineView.h"
#import "JXCategoryFactory.h"
@implementation JXCategoryIndicatorRainbowLineView
- (void)jx_refreshState:(JXCategoryIndicatorParamsModel *)model {
[super jx_refreshState:model];
UIColor *color = self.indicatorColors[model.selectedIndex];
self.backgroundColor = color;
}
- (void)jx_contentScrollViewDidScroll:(JXCategoryIndicatorParamsModel *)model {
[super jx_contentScrollViewDidScroll:model];
UIColor *leftColor = self.indicatorColors[model.leftIndex];
UIColor *rightColor = self.indicatorColors[model.rightIndex];
UIColor *color = [JXCategoryFactory interpolationColorFrom:leftColor to:rightColor percent:model.percent];
self.backgroundColor = color;
}
- (void)jx_selectedCell:(JXCategoryIndicatorParamsModel *)model {
[super jx_selectedCell:model];
UIColor *color = self.indicatorColors[model.selectedIndex];
self.backgroundColor = color;
}
@end
//
// JXCategoryIndicatorTriangleView.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorComponentView.h"
@interface JXCategoryIndicatorTriangleView : JXCategoryIndicatorComponentView
@property (nonatomic, assign) CGSize triangleViewSize; //默认:CGSizeMake(14, 10)
@property (nonatomic, strong) UIColor *triangleViewColor; //默认:[UIColor redColor]
@end
//
// JXCategoryIndicatorTriangleView.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorTriangleView.h"
#import "JXCategoryFactory.h"
@interface JXCategoryIndicatorTriangleView ()
@property (nonatomic, strong) CAShapeLayer *triangleLayer;
@end
@implementation JXCategoryIndicatorTriangleView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_triangleViewSize = CGSizeMake(14, 10);
_triangleViewColor = [UIColor redColor];
_triangleLayer = [CAShapeLayer layer];
[self.layer addSublayer:self.triangleLayer];
}
return self;
}
#pragma mark - JXCategoryIndicatorProtocol
- (void)jx_refreshState:(JXCategoryIndicatorParamsModel *)model {
CGFloat x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - self.triangleViewSize.width)/2;
CGFloat y = self.superview.bounds.size.height - self.triangleViewSize.height - self.verticalMargin;
if (self.componentPosition == JXCategoryComponentPosition_Top) {
y = self.verticalMargin;
}
self.frame = CGRectMake(x, y, self.triangleViewSize.width, self.triangleViewSize.height);
[CATransaction begin];
[CATransaction setDisableActions:NO];
self.triangleLayer.fillColor = self.triangleViewColor.CGColor;
self.triangleLayer.frame = self.bounds;
UIBezierPath *path = [UIBezierPath bezierPath];
if (self.componentPosition == JXCategoryComponentPosition_Bottom) {
[path moveToPoint:CGPointMake(self.bounds.size.width/2, 0)];
[path addLineToPoint:CGPointMake(0, self.bounds.size.height)];
[path addLineToPoint:CGPointMake(self.bounds.size.width, self.bounds.size.height)];
}else {
[path moveToPoint:CGPointMake(0, 0)];
[path addLineToPoint:CGPointMake(self.bounds.size.width, 0)];
[path addLineToPoint:CGPointMake(self.bounds.size.width/2, self.bounds.size.height)];
}
[path closePath];
self.triangleLayer.path = path.CGPath;
[CATransaction commit];
}
- (void)jx_contentScrollViewDidScroll:(JXCategoryIndicatorParamsModel *)model {
CGRect rightCellFrame = model.rightCellFrame;
CGRect leftCellFrame = model.leftCellFrame;
CGFloat percent = model.percent;
CGFloat targetWidth = self.triangleViewSize.width;
CGFloat targetX = 0;
if (percent == 0) {
targetX = leftCellFrame.origin.x + (leftCellFrame.size.width - targetWidth)/2.0;
}else {
CGFloat leftX = leftCellFrame.origin.x + (leftCellFrame.size.width - targetWidth)/2;
CGFloat rightX = rightCellFrame.origin.x + (rightCellFrame.size.width - targetWidth)/2;
targetX = [JXCategoryFactory interpolationFrom:leftX to:rightX percent:percent];
}
//允许变动frame的情况:1、允许滚动;2、不允许滚动,但是已经通过手势滚动切换一页内容了;
if (self.scrollEnabled == YES || (self.scrollEnabled == NO && percent == 0)) {
CGRect frame = self.frame;
frame.origin.x = targetX;
self.frame = frame;
}
}
- (void)jx_selectedCell:(JXCategoryIndicatorParamsModel *)model {
CGRect toFrame = self.frame;
toFrame.origin.x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - self.triangleViewSize.width)/2;
if (self.scrollEnabled) {
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
self.frame = toFrame;
} completion:^(BOOL finished) {
}];
}else {
self.frame = toFrame;
}
}
@end
//
// JXCategoryViewAnimator.h
// JXCategoryView
//
// Created by jiaxin on 2019/1/24.
// Copyright © 2019 jiaxin. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface JXCategoryViewAnimator : NSObject
@property (nonatomic, assign) NSTimeInterval duration;
@property (nonatomic, copy) void(^progressCallback)(CGFloat percent);
@property (nonatomic, copy) void(^completeCallback)(void);
@property (readonly, getter=isExecuting) BOOL executing;
- (void)start;
- (void)stop;
- (void)invalid;
@end
//
// JXCategoryViewAnimator.m
// JXCategoryView
//
// Created by jiaxin on 2019/1/24.
// Copyright © 2019 jiaxin. All rights reserved.
//
#import "JXCategoryViewAnimator.h"
@interface JXCategoryViewAnimator ()
@property (nonatomic, strong) CADisplayLink *displayLink;
@property (nonatomic, assign) CFTimeInterval firstTimestamp;
@property (readwrite, getter=isExecuting) BOOL executing;
@end
@implementation JXCategoryViewAnimator
- (void)dealloc
{
self.progressCallback = nil;
self.completeCallback = nil;
}
- (instancetype)init
{
self = [super init];
if (self) {
_executing = NO;
_duration = 0.25;
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(processDisplayLink:)];
}
return self;
}
- (void)start {
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
self.executing = YES;
}
- (void)stop {
!self.progressCallback ?: self.progressCallback(1);
[self.displayLink invalidate];
!self.completeCallback ?: self.completeCallback();
self.executing = NO;
}
- (void)invalid {
[self.displayLink invalidate];
!self.completeCallback ?: self.completeCallback();
self.executing = NO;
}
- (void)processDisplayLink:(CADisplayLink *)sender {
if (self.firstTimestamp == 0) {
self.firstTimestamp = sender.timestamp;
return;
}
CGFloat percent = (sender.timestamp - self.firstTimestamp)/self.duration;
if (percent >= 1) {
!self.progressCallback ?: self.progressCallback(percent);
[self.displayLink invalidate];
!self.completeCallback ?: self.completeCallback();
self.executing = NO;
}else {
!self.progressCallback ?: self.progressCallback(percent);
self.executing = YES;
}
}
@end
//
// JXCategoryComponetCell.h
// DQGuess
//
// Created by jiaxin on 2018/7/25.
// Copyright © 2018年 jingbo. All rights reserved.
//
#import "JXCategoryBaseCell.h"
@interface JXCategoryIndicatorCell : JXCategoryBaseCell
@end
//
// JXCategoryComponetCell.m
// DQGuess
//
// Created by jiaxin on 2018/7/25.
// Copyright © 2018年 jingbo. All rights reserved.
//
#import "JXCategoryIndicatorCell.h"
#import "JXCategoryIndicatorCellModel.h"
@interface JXCategoryIndicatorCell ()
@property (nonatomic, strong) UIView *separatorLine;
@end
@implementation JXCategoryIndicatorCell
- (void)initializeViews
{
[super initializeViews];
self.separatorLine = [[UIView alloc] init];
self.separatorLine.hidden = YES;
[self.contentView addSubview:self.separatorLine];
}
- (void)layoutSubviews
{
[super layoutSubviews];
JXCategoryIndicatorCellModel *model = (JXCategoryIndicatorCellModel *)self.cellModel;
CGFloat lineWidth = model.separatorLineSize.width;
CGFloat lineHeight = model.separatorLineSize.height;
self.separatorLine.frame = CGRectMake(self.bounds.size.width - lineWidth + self.cellModel.cellSpacing/2, (self.bounds.size.height - lineHeight)/2.0, lineWidth, lineHeight);
}
- (void)reloadData:(JXCategoryBaseCellModel *)cellModel {
[super reloadData:cellModel];
JXCategoryIndicatorCellModel *model = (JXCategoryIndicatorCellModel *)cellModel;
self.separatorLine.backgroundColor = model.separatorLineColor;
self.separatorLine.hidden = !model.sepratorLineShowEnabled;
if (model.cellBackgroundColorGradientEnabled) {
if (model.selected) {
self.contentView.backgroundColor = model.cellBackgroundSelectedColor;
}else {
self.contentView.backgroundColor = model.cellBackgroundUnselectedColor;
}
}
}
@end
//
// JXCategoryComponentCellModel.h
// DQGuess
//
// Created by jiaxin on 2018/7/25.
// Copyright © 2018年 jingbo. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "JXCategoryBaseCellModel.h"
@interface JXCategoryIndicatorCellModel : JXCategoryBaseCellModel
@property (nonatomic, assign) BOOL sepratorLineShowEnabled;
@property (nonatomic, strong) UIColor *separatorLineColor;
@property (nonatomic, assign) CGSize separatorLineSize;
@property (nonatomic, assign) CGRect backgroundViewMaskFrame;
@property (nonatomic, assign) BOOL cellBackgroundColorGradientEnabled;
@property (nonatomic, strong) UIColor *cellBackgroundUnselectedColor;
@property (nonatomic, strong) UIColor *cellBackgroundSelectedColor;
@end
//
// JXCategoryComponentCellModel.m
// DQGuess
//
// Created by jiaxin on 2018/7/25.
// Copyright © 2018年 jingbo. All rights reserved.
//
#import "JXCategoryIndicatorCellModel.h"
@implementation JXCategoryIndicatorCellModel
@end
//
// JXCategoryComponentView.h
// DQGuess
//
// Created by jiaxin on 2018/7/25.
// Copyright © 2018年 jingbo. All rights reserved.
//
#import "JXCategoryBaseView.h"
#import "JXCategoryIndicatorCell.h"
#import "JXCategoryIndicatorCellModel.h"
#import "JXCategoryIndicatorProtocol.h"
@interface JXCategoryIndicatorView : JXCategoryBaseView
@property (nonatomic, strong) NSArray <UIView<JXCategoryIndicatorProtocol> *> *indicators;
//----------------------ellBackgroundColor-----------------------//
@property (nonatomic, assign) BOOL cellBackgroundColorGradientEnabled; //默认:NO
@property (nonatomic, strong) UIColor *cellBackgroundUnselectedColor; //默认:[UIColor clearColor]
@property (nonatomic, strong) UIColor *cellBackgroundSelectedColor; //默认:[UIColor grayColor]
//----------------------separatorLine-----------------------//
@property (nonatomic, assign) BOOL separatorLineShowEnabled; //默认为NO
@property (nonatomic, strong) UIColor *separatorLineColor; //默认为[UIColor lightGrayColor]
@property (nonatomic, assign) CGSize separatorLineSize; //默认为CGSizeMake(1/[UIScreen mainScreen].scale, 20)
/**
当contentScrollView滚动时候,处理跟随手势的过渡效果。
根据cellModel的左右位置、是否选中、ratio进行过滤数据计算。
@param leftCellModel 左边的cellModel
@param rightCellModel 右边的cellModel
@param ratio 从左往右方向计算的百分比
*/
- (void)refreshLeftCellModel:(JXCategoryBaseCellModel *)leftCellModel rightCellModel:(JXCategoryBaseCellModel *)rightCellModel ratio:(CGFloat)ratio NS_REQUIRES_SUPER;
@end
//
// JXCategoryComponentView.m
// DQGuess
//
// Created by jiaxin on 2018/7/25.
// Copyright © 2018年 jingbo. All rights reserved.
//
#import "JXCategoryIndicatorView.h"
#import "JXCategoryIndicatorBackgroundView.h"
#import "JXCategoryFactory.h"
@interface JXCategoryIndicatorView()
@property (nonatomic, strong) CALayer *backgroundEllipseLayer;
@end
@implementation JXCategoryIndicatorView
- (void)initializeData {
[super initializeData];
_separatorLineShowEnabled = NO;
_separatorLineColor = [UIColor lightGrayColor];
_separatorLineSize = CGSizeMake(1/[UIScreen mainScreen].scale, 20);
_cellBackgroundColorGradientEnabled = NO;
_cellBackgroundUnselectedColor = [UIColor whiteColor];
_cellBackgroundSelectedColor = [UIColor lightGrayColor];
}
- (void)initializeViews {
[super initializeViews];
}
- (void)setIndicators:(NSArray<UIView<JXCategoryIndicatorProtocol> *> *)indicators {
for (UIView *component in self.indicators) {
//先移除之前的component
[component removeFromSuperview];
}
_indicators = indicators;
for (UIView *component in self.indicators) {
[self.collectionView addSubview:component];
}
self.collectionView.indicators = indicators;
}
- (void)refreshState {
[super refreshState];
CGRect selectedCellFrame = CGRectZero;
JXCategoryIndicatorCellModel *selectedCellModel = nil;
for (int i = 0; i < self.dataSource.count; i++) {
JXCategoryIndicatorCellModel *cellModel = (JXCategoryIndicatorCellModel *)self.dataSource[i];
cellModel.sepratorLineShowEnabled = self.separatorLineShowEnabled;
cellModel.separatorLineColor = self.separatorLineColor;
cellModel.separatorLineSize = self.separatorLineSize;
cellModel.backgroundViewMaskFrame = CGRectZero;
cellModel.cellBackgroundColorGradientEnabled = self.cellBackgroundColorGradientEnabled;
cellModel.cellBackgroundSelectedColor = self.cellBackgroundSelectedColor;
cellModel.cellBackgroundUnselectedColor = self.cellBackgroundUnselectedColor;
if (i == self.dataSource.count - 1) {
cellModel.sepratorLineShowEnabled = NO;
}
if (i == self.selectedIndex) {
selectedCellModel = cellModel;
cellModel.selected = YES;
selectedCellFrame = [self getTargetCellFrame:i];
}
}
for (UIView<JXCategoryIndicatorProtocol> *component in self.indicators) {
if (self.dataSource.count <= 0) {
component.hidden = YES;
}else {
component.hidden = NO;
JXCategoryIndicatorParamsModel *indicatorParamsModel = [[JXCategoryIndicatorParamsModel alloc] init];
indicatorParamsModel.selectedIndex = self.selectedIndex;
indicatorParamsModel.selectedCellFrame = selectedCellFrame;
[component jx_refreshState:indicatorParamsModel];
if ([component isKindOfClass:[JXCategoryIndicatorBackgroundView class]]) {
CGRect maskFrame = component.frame;
maskFrame.origin.x = maskFrame.origin.x - selectedCellFrame.origin.x;
selectedCellModel.backgroundViewMaskFrame = maskFrame;
}
}
}
}
- (void)refreshSelectedCellModel:(JXCategoryBaseCellModel *)selectedCellModel unselectedCellModel:(JXCategoryBaseCellModel *)unselectedCellModel {
[super refreshSelectedCellModel:selectedCellModel unselectedCellModel:unselectedCellModel];
JXCategoryIndicatorCellModel *myUnselectedCellModel = (JXCategoryIndicatorCellModel *)unselectedCellModel;
myUnselectedCellModel.backgroundViewMaskFrame = CGRectZero;
myUnselectedCellModel.cellBackgroundUnselectedColor = self.cellBackgroundUnselectedColor;
myUnselectedCellModel.cellBackgroundSelectedColor = self.cellBackgroundSelectedColor;
JXCategoryIndicatorCellModel *myselectedCellModel = (JXCategoryIndicatorCellModel *)selectedCellModel;
myselectedCellModel.cellBackgroundUnselectedColor = self.cellBackgroundUnselectedColor;
myselectedCellModel.cellBackgroundSelectedColor = self.cellBackgroundSelectedColor;
}
- (void)contentOffsetOfContentScrollViewDidChanged:(CGPoint)contentOffset {
[super contentOffsetOfContentScrollViewDidChanged:contentOffset];
CGFloat ratio = contentOffset.x/self.contentScrollView.bounds.size.width;
if (ratio > self.dataSource.count - 1 || ratio < 0) {
//超过了边界,不需要处理
return;
}
ratio = MAX(0, MIN(self.dataSource.count - 1, ratio));
NSInteger baseIndex = floorf(ratio);
if (baseIndex + 1 >= self.dataSource.count) {
//右边越界了,不需要处理
return;
}
CGFloat remainderRatio = ratio - baseIndex;
CGRect leftCellFrame = [self getTargetCellFrame:baseIndex];
CGRect rightCellFrame = [self getTargetCellFrame:baseIndex + 1];
JXCategoryIndicatorParamsModel *indicatorParamsModel = [[JXCategoryIndicatorParamsModel alloc] init];
indicatorParamsModel.selectedIndex = self.selectedIndex;
indicatorParamsModel.leftIndex = baseIndex;
indicatorParamsModel.leftCellFrame = leftCellFrame;
indicatorParamsModel.rightIndex = baseIndex + 1;
indicatorParamsModel.rightCellFrame = rightCellFrame;
indicatorParamsModel.percent = remainderRatio;
if (remainderRatio == 0) {
for (UIView<JXCategoryIndicatorProtocol> *component in self.indicators) {
[component jx_contentScrollViewDidScroll:indicatorParamsModel];
}
}else {
JXCategoryIndicatorCellModel *leftCellModel = (JXCategoryIndicatorCellModel *)self.dataSource[baseIndex];
JXCategoryIndicatorCellModel *rightCellModel = (JXCategoryIndicatorCellModel *)self.dataSource[baseIndex + 1];
[self refreshLeftCellModel:leftCellModel rightCellModel:rightCellModel ratio:remainderRatio];
for (UIView<JXCategoryIndicatorProtocol> *component in self.indicators) {
[component jx_contentScrollViewDidScroll:indicatorParamsModel];
if ([component isKindOfClass:[JXCategoryIndicatorBackgroundView class]]) {
CGRect leftMaskFrame = component.frame;
leftMaskFrame.origin.x = leftMaskFrame.origin.x - leftCellFrame.origin.x;
leftCellModel.backgroundViewMaskFrame = leftMaskFrame;
CGRect rightMaskFrame = component.frame;
rightMaskFrame.origin.x = rightMaskFrame.origin.x - rightCellFrame.origin.x;
rightCellModel.backgroundViewMaskFrame = rightMaskFrame;
}
}
JXCategoryBaseCell *leftCell = (JXCategoryBaseCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:baseIndex inSection:0]];
[leftCell reloadData:leftCellModel];
JXCategoryBaseCell *rightCell = (JXCategoryBaseCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:baseIndex + 1 inSection:0]];
[rightCell reloadData:rightCellModel];
}
}
- (BOOL)selectCellAtIndex:(NSInteger)index isClicked:(BOOL)isClicked {
NSInteger lastSelectedIndex = self.selectedIndex;
BOOL result = [super selectCellAtIndex:index isClicked:isClicked];
if (!result) {
return NO;
}
CGRect clickedCellFrame = [self getTargetCellFrame:index];
JXCategoryIndicatorCellModel *selectedCellModel = (JXCategoryIndicatorCellModel *)self.dataSource[index];
for (UIView<JXCategoryIndicatorProtocol> *component in self.indicators) {
JXCategoryIndicatorParamsModel *indicatorParamsModel = [[JXCategoryIndicatorParamsModel alloc] init];
indicatorParamsModel.lastSelectedIndex = lastSelectedIndex;
indicatorParamsModel.selectedIndex = index;
indicatorParamsModel.selectedCellFrame = clickedCellFrame;
indicatorParamsModel.isClicked = isClicked;
[component jx_selectedCell:indicatorParamsModel];
if ([component isKindOfClass:[JXCategoryIndicatorBackgroundView class]]) {
CGRect maskFrame = component.frame;
maskFrame.origin.x = maskFrame.origin.x - clickedCellFrame.origin.x;
selectedCellModel.backgroundViewMaskFrame = maskFrame;
}
}
JXCategoryIndicatorCell *selectedCell = (JXCategoryIndicatorCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]];
[selectedCell reloadData:selectedCellModel];
return YES;
}
- (void)refreshLeftCellModel:(JXCategoryBaseCellModel *)leftCellModel rightCellModel:(JXCategoryBaseCellModel *)rightCellModel ratio:(CGFloat)ratio {
if (self.cellBackgroundColorGradientEnabled) {
//处理cell背景色渐变
JXCategoryIndicatorCellModel *leftModel = (JXCategoryIndicatorCellModel *)leftCellModel;
JXCategoryIndicatorCellModel *rightModel = (JXCategoryIndicatorCellModel *)rightCellModel;
if (leftModel.selected) {
leftModel.cellBackgroundSelectedColor = [JXCategoryFactory interpolationColorFrom:self.cellBackgroundSelectedColor to:self.cellBackgroundUnselectedColor percent:ratio];
leftModel.cellBackgroundUnselectedColor = self.cellBackgroundUnselectedColor;
}else {
leftModel.cellBackgroundUnselectedColor = [JXCategoryFactory interpolationColorFrom:self.cellBackgroundSelectedColor to:self.cellBackgroundUnselectedColor percent:ratio];
leftModel.cellBackgroundSelectedColor = self.cellBackgroundSelectedColor;
}
if (rightModel.selected) {
rightModel.cellBackgroundSelectedColor = [JXCategoryFactory interpolationColorFrom:self.cellBackgroundUnselectedColor to:self.cellBackgroundSelectedColor percent:ratio];
rightModel.cellBackgroundUnselectedColor = self.cellBackgroundUnselectedColor;
}else {
rightModel.cellBackgroundUnselectedColor = [JXCategoryFactory interpolationColorFrom:self.cellBackgroundUnselectedColor to:self.cellBackgroundSelectedColor percent:ratio];
rightModel.cellBackgroundSelectedColor = self.cellBackgroundSelectedColor;
}
}
}
@end
#import "JXCategoryBaseView.h"
#import "JXCategoryIndicatorView.h"
#import "JXCategoryTitleView.h"
#import "JXCategoryImageView.h"
#import "JXCategoryTitleImageView.h"
#import "JXCategoryNumberView.h"
#import "JXCategoryDotView.h"
#import "JXCategoryFactory.h"
#import "JXCategoryIndicatorProtocol.h"
#import "JXCategoryViewDefines.h"
#import "JXCategoryListVCContainerView.h"
#import "JXCategoryListContainerView.h"
#import "JXCategoryIndicatorComponentView.h"
#import "JXCategoryIndicatorLineView.h"
#import "JXCategoryIndicatorTriangleView.h"
#import "JXCategoryIndicatorImageView.h"
#import "JXCategoryIndicatorBackgroundView.h"
#import "JXCategoryIndicatorBallView.h"
#import "JXCategoryIndicatorRainbowLineView.h"
//
// JXCategoryNumberCell.h
// DQGuess
//
// Created by jiaxin on 2018/4/9.
// Copyright © 2018年 jingbo. All rights reserved.
//
#import "JXCategoryTitleCell.h"
@interface JXCategoryNumberCell : JXCategoryTitleCell
@property (nonatomic, strong) UILabel *numberLabel;
@end
//
// JXCategoryNumberCell.m
// DQGuess
//
// Created by jiaxin on 2018/4/9.
// Copyright © 2018年 jingbo. All rights reserved.
//
#import "JXCategoryNumberCell.h"
#import "JXCategoryNumberCellModel.h"
@interface JXCategoryNumberCell ()
@end
@implementation JXCategoryNumberCell
- (void)initializeViews {
[super initializeViews];
self.numberLabel = ({
UILabel *label = [[UILabel alloc] init];
label.textAlignment = NSTextAlignmentCenter;
label.layer.masksToBounds = YES;
label;
});
[self.contentView addSubview:self.numberLabel];
}
- (void)layoutSubviews {
[super layoutSubviews];
[self.numberLabel sizeToFit];
JXCategoryNumberCellModel *myCellModel = (JXCategoryNumberCellModel *)self.cellModel;
self.numberLabel.bounds = CGRectMake(0, 0, self.numberLabel.bounds.size.width + myCellModel.numberLabelWidthIncrement, myCellModel.numberLabelHeight);
self.numberLabel.layer.cornerRadius = myCellModel.numberLabelHeight/2.0;
self.numberLabel.center = CGPointMake(CGRectGetMaxX(self.titleLabel.frame), CGRectGetMinY(self.titleLabel.frame));
}
- (void)reloadData:(JXCategoryBaseCellModel *)cellModel {
[super reloadData:cellModel];
JXCategoryNumberCellModel *myCellModel = (JXCategoryNumberCellModel *)cellModel;
self.numberLabel.hidden = myCellModel.count == 0;
self.numberLabel.backgroundColor = myCellModel.numberBackgroundColor;
self.numberLabel.font = myCellModel.numberLabelFont;
self.numberLabel.textColor = myCellModel.numberTitleColor;
self.numberLabel.text = [NSString stringWithFormat:@"%ld", (long)myCellModel.count];
if (myCellModel.count >= 1000) {
self.numberLabel.text = @"999+";
}
[self setNeedsLayout];
[self layoutIfNeeded];
}
@end
//
// JXCategoryNumberCellModel.h
// DQGuess
//
// Created by jiaxin on 2018/4/24.
// Copyright © 2018年 jingbo. All rights reserved.
//
#import "JXCategoryTitleCellModel.h"
@interface JXCategoryNumberCellModel : JXCategoryTitleCellModel
@property (nonatomic, assign) NSInteger count;
@property (nonatomic, strong) UIColor *numberBackgroundColor;
@property (nonatomic, strong) UIColor *numberTitleColor;
@property (nonatomic, assign) CGFloat numberLabelWidthIncrement;
@property (nonatomic, assign) CGFloat numberLabelHeight;
@property (nonatomic, strong) UIFont *numberLabelFont;
@end
//
// JXCategoryNumberCellModel.m
// DQGuess
//
// Created by jiaxin on 2018/4/24.
// Copyright © 2018年 jingbo. All rights reserved.
//
#import "JXCategoryNumberCellModel.h"
@implementation JXCategoryNumberCellModel
@end
//
// JXCategoryNumberView.h
// DQGuess
//
// Created by jiaxin on 2018/4/9.
// Copyright © 2018年 jingbo. All rights reserved.
//
#import "JXCategoryTitleView.h"
#import "JXCategoryNumberCell.h"
#import "JXCategoryNumberCellModel.h"
@interface JXCategoryNumberView : JXCategoryTitleView
/**
需要与titles的count对应
*/
@property (nonatomic, strong) NSArray <NSNumber *> *counts;
/**
numberLabel的font,默认:[UIFont systemFontOfSize:11]
*/
@property (nonatomic, strong) UIFont *numberLabelFont;
/**
数字的背景色,默认:[UIColor colorWithRed:241/255.0 green:147/255.0 blue:95/255.0 alpha:1]
*/
@property (nonatomic, strong) UIColor *numberBackgroundColor;
/**
数字的title颜色,默认:[UIColor whiteColor]
*/
@property (nonatomic, strong) UIColor *numberTitleColor;
/**
numberLabel的宽度补偿,label真实的宽度是文字内容的宽度加上补偿的宽度,默认:10
*/
@property (nonatomic, assign) CGFloat numberLabelWidthIncrement;
/**
numberLabel的高度,默认:14
*/
@property (nonatomic, assign) CGFloat numberLabelHeight;
@end
//
// JXCategoryNumberView.m
// DQGuess
//
// Created by jiaxin on 2018/4/9.
// Copyright © 2018年 jingbo. All rights reserved.
//
#import "JXCategoryNumberView.h"
@implementation JXCategoryNumberView
- (void)initializeData {
[super initializeData];
self.cellSpacing = 25;
_numberTitleColor = [UIColor whiteColor];
_numberBackgroundColor = [UIColor colorWithRed:241/255.0 green:147/255.0 blue:95/255.0 alpha:1];
_numberLabelHeight = 14;
_numberLabelWidthIncrement = 10;
_numberLabelFont = [UIFont systemFontOfSize:11];
}
- (Class)preferredCellClass {
return [JXCategoryNumberCell class];
}
- (void)refreshDataSource {
NSMutableArray *tempArray = [NSMutableArray array];
for (int i = 0; i < self.titles.count; i++) {
JXCategoryNumberCellModel *cellModel = [[JXCategoryNumberCellModel alloc] init];
[tempArray addObject:cellModel];
}
self.dataSource = tempArray;
}
- (void)refreshCellModel:(JXCategoryBaseCellModel *)cellModel index:(NSInteger)index {
[super refreshCellModel:cellModel index:index];
JXCategoryNumberCellModel *myCellModel = (JXCategoryNumberCellModel *)cellModel;
myCellModel.count = [self.counts[index] integerValue];
myCellModel.numberBackgroundColor = self.numberBackgroundColor;
myCellModel.numberTitleColor = self.numberTitleColor;
myCellModel.numberLabelHeight = self.numberLabelHeight;
myCellModel.numberLabelWidthIncrement = self.numberLabelWidthIncrement;
myCellModel.numberLabelFont = self.numberLabelFont;
}
@end
//
// JXCategoryTitleCell.h
// UI系列测试
//
// Created by jiaxin on 2018/3/15.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorCell.h"
@interface JXCategoryTitleCell : JXCategoryIndicatorCell
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UILabel *maskTitleLabel;
@end
//
// JXCategoryTitleCell.m
// UI系列测试
//
// Created by jiaxin on 2018/3/15.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryTitleCell.h"
#import "JXCategoryTitleCellModel.h"
@interface JXCategoryTitleCell ()
@property (nonatomic, strong) CALayer *maskLayer;
@end
@implementation JXCategoryTitleCell
- (void)initializeViews
{
[super initializeViews];
_titleLabel = [[UILabel alloc] init];
self.titleLabel.textAlignment = NSTextAlignmentCenter;
[self.contentView addSubview:self.titleLabel];
_maskTitleLabel = [[UILabel alloc] init];
_maskTitleLabel.hidden = YES;
self.maskTitleLabel.textAlignment = NSTextAlignmentCenter;
[self.contentView addSubview:self.maskTitleLabel];
_maskLayer = [CALayer layer];
self.maskLayer.backgroundColor = [UIColor redColor].CGColor;
self.maskTitleLabel.layer.mask = self.maskLayer;
}
- (void)layoutSubviews {
[super layoutSubviews];
self.titleLabel.center = self.contentView.center;
self.maskTitleLabel.center = self.contentView.center;
}
- (void)reloadData:(JXCategoryBaseCellModel *)cellModel {
[super reloadData:cellModel];
JXCategoryTitleCellModel *myCellModel = (JXCategoryTitleCellModel *)cellModel;
CGFloat pointSize = myCellModel.titleFont.pointSize;
UIFontDescriptor *fontDescriptor = myCellModel.titleFont.fontDescriptor;
if (myCellModel.selected) {
fontDescriptor = myCellModel.titleSelectedFont.fontDescriptor;
pointSize = myCellModel.titleSelectedFont.pointSize;
}
if (myCellModel.titleLabelZoomEnabled) {
self.titleLabel.font = [UIFont fontWithDescriptor:fontDescriptor size:pointSize*myCellModel.titleLabelZoomScale];
self.maskTitleLabel.font = [UIFont fontWithDescriptor:fontDescriptor size:pointSize*myCellModel.titleLabelZoomScale];
}else {
self.titleLabel.font = [UIFont fontWithDescriptor:fontDescriptor size:pointSize];
self.maskTitleLabel.font = [UIFont fontWithDescriptor:fontDescriptor size:pointSize];
}
NSString *titleString = myCellModel.title ? myCellModel.title : @"";
NSMutableAttributedString *attriString = [[NSMutableAttributedString alloc] initWithString:titleString];
if (myCellModel.titleLabelStrokeWidthEnabled) {
[attriString addAttribute:NSStrokeWidthAttributeName value:@(myCellModel.titleLabelSelectedStrokeWidth) range:NSMakeRange(0, myCellModel.title.length)];
}
self.maskTitleLabel.hidden = !myCellModel.titleLabelMaskEnabled;
if (myCellModel.titleLabelMaskEnabled) {
self.titleLabel.textColor = myCellModel.titleColor;
self.maskTitleLabel.font = myCellModel.titleFont;
self.maskTitleLabel.textColor = myCellModel.titleSelectedColor;
self.maskTitleLabel.attributedText = attriString;
[self.maskTitleLabel sizeToFit];
CGRect frame = myCellModel.backgroundViewMaskFrame;
frame.origin.x -= (self.contentView.bounds.size.width - self.maskTitleLabel.bounds.size.width)/2;
frame.origin.y = 0;
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.maskLayer.frame = frame;
[CATransaction commit];
}else {
if (myCellModel.selected) {
self.titleLabel.textColor = myCellModel.titleSelectedColor;
}else {
self.titleLabel.textColor = myCellModel.titleColor;
}
}
self.titleLabel.attributedText = attriString;
[self.titleLabel sizeToFit];
[self setNeedsLayout];
[self layoutIfNeeded];
}
@end
//
// JXCategoryTitleCellModel.h
// UI系列测试
//
// Created by jiaxin on 2018/3/15.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorCellModel.h"
#import <UIKit/UIKit.h>
@interface JXCategoryTitleCellModel : JXCategoryIndicatorCellModel
@property (nonatomic, copy) NSString *title;
@property (nonatomic, strong) UIColor *titleColor;
@property (nonatomic, strong) UIColor *titleSelectedColor;
@property (nonatomic, strong) UIFont *titleFont;
@property (nonatomic, strong) UIFont *titleSelectedFont;
@property (nonatomic, assign) BOOL titleLabelMaskEnabled;
@property (nonatomic, strong) CALayer *backgroundEllipseLayer;
@property (nonatomic, assign) BOOL titleLabelZoomEnabled;
@property (nonatomic, assign) CGFloat titleLabelZoomScale;
@property (nonatomic, assign) CGFloat titleLabelStrokeWidthEnabled;
@property (nonatomic, assign) CGFloat titleLabelSelectedStrokeWidth;
@end
//
// JXCategoryTitleCellModel.m
// UI系列测试
//
// Created by jiaxin on 2018/3/15.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryTitleCellModel.h"
@implementation JXCategoryTitleCellModel
@end
//
// JXCategoryView.h
// UI系列测试
//
// Created by jiaxin on 2018/3/15.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorView.h"
#import "JXCategoryTitleCell.h"
#import "JXCategoryTitleCellModel.h"
@interface JXCategoryTitleView : JXCategoryIndicatorView
@property (nonatomic, strong) NSArray <NSString *>*titles;
@property (nonatomic, strong) UIColor *titleColor; //默认:[UIColor blackColor]
@property (nonatomic, strong) UIColor *titleSelectedColor; //默认:[UIColor redColor]
@property (nonatomic, strong) UIFont *titleFont; //默认:[UIFont systemFontOfSize:15]
@property (nonatomic, strong) UIFont *titleSelectedFont; //文字被选中的字体。默认:与titleFont一样
@property (nonatomic, assign) BOOL titleColorGradientEnabled; //默认:NO,title的颜色是否渐变过渡
@property (nonatomic, assign) BOOL titleLabelMaskEnabled; //默认:NO,titleLabel是否遮罩过滤。(需要backgroundEllipseLayerShowEnabled = YES)
//----------------------titleLabelZoomEnabled-----------------------//
@property (nonatomic, assign) BOOL titleLabelZoomEnabled; //默认为NO
@property (nonatomic, assign) BOOL titleLabelZoomScrollGradientEnabled; //手势滚动中,是否需要更新状态。默认为YES
@property (nonatomic, assign) CGFloat titleLabelZoomScale; //默认1.2,titleLabelZoomEnabled为YES才生效
//----------------------titleLabelStrokeWidth-----------------------//
@property (nonatomic, assign) CGFloat titleLabelStrokeWidthEnabled; //默认:NO
@property (nonatomic, assign) CGFloat titleLabelSelectedStrokeWidth; //默认:-3,用于控制字体的粗细(底层通过NSStrokeWidthAttributeName实现)。使用该属性,务必让titleFont和titleSelectedFont设置为一样的!!!
@end
//
// JXCategoryView.m
// UI系列测试
//
// Created by jiaxin on 2018/3/15.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryTitleView.h"
#import "JXCategoryFactory.h"
@interface JXCategoryTitleView ()
@end
@implementation JXCategoryTitleView
- (void)initializeData
{
[super initializeData];
_titleLabelZoomEnabled = NO;
_titleLabelZoomScale = 1.2;
_titleColor = [UIColor blackColor];
_titleSelectedColor = [UIColor redColor];
_titleFont = [UIFont systemFontOfSize:15];
_titleColorGradientEnabled = NO;
_titleLabelMaskEnabled = NO;
_titleLabelZoomScrollGradientEnabled = YES;
_titleLabelStrokeWidthEnabled = NO;
_titleLabelSelectedStrokeWidth = -3;
}
- (UIFont *)titleSelectedFont {
if (_titleSelectedFont != nil) {
return _titleSelectedFont;
}
return self.titleFont;
}
#pragma mark - Override
- (Class)preferredCellClass {
return [JXCategoryTitleCell class];
}
- (void)refreshDataSource {
NSMutableArray *tempArray = [NSMutableArray array];
for (int i = 0; i < self.titles.count; i++) {
JXCategoryTitleCellModel *cellModel = [[JXCategoryTitleCellModel alloc] init];
[tempArray addObject:cellModel];
}
self.dataSource = tempArray;
}
- (void)refreshSelectedCellModel:(JXCategoryBaseCellModel *)selectedCellModel unselectedCellModel:(JXCategoryBaseCellModel *)unselectedCellModel {
[super refreshSelectedCellModel:selectedCellModel unselectedCellModel:unselectedCellModel];
JXCategoryTitleCellModel *myUnselectedCellModel = (JXCategoryTitleCellModel *)unselectedCellModel;
myUnselectedCellModel.titleColor = self.titleColor;
myUnselectedCellModel.titleSelectedColor = self.titleSelectedColor;
myUnselectedCellModel.titleLabelZoomScale = 1.0;
myUnselectedCellModel.titleLabelSelectedStrokeWidth = 0;
JXCategoryTitleCellModel *myselectedCellModel = (JXCategoryTitleCellModel *)selectedCellModel;
myselectedCellModel.titleColor = self.titleColor;
myselectedCellModel.titleSelectedColor = self.titleSelectedColor;
myselectedCellModel.titleLabelZoomScale = self.titleLabelZoomScale;
myselectedCellModel.titleLabelSelectedStrokeWidth = self.titleLabelSelectedStrokeWidth;
}
- (void)refreshLeftCellModel:(JXCategoryBaseCellModel *)leftCellModel rightCellModel:(JXCategoryBaseCellModel *)rightCellModel ratio:(CGFloat)ratio {
[super refreshLeftCellModel:leftCellModel rightCellModel:rightCellModel ratio:ratio];
JXCategoryTitleCellModel *leftModel = (JXCategoryTitleCellModel *)leftCellModel;
JXCategoryTitleCellModel *rightModel = (JXCategoryTitleCellModel *)rightCellModel;
if (self.titleLabelZoomEnabled && self.titleLabelZoomScrollGradientEnabled) {
leftModel.titleLabelZoomScale = [JXCategoryFactory interpolationFrom:self.titleLabelZoomScale to:1.0 percent:ratio];
rightModel.titleLabelZoomScale = [JXCategoryFactory interpolationFrom:1.0 to:self.titleLabelZoomScale percent:ratio];
}
if (self.titleLabelStrokeWidthEnabled) {
leftModel.titleLabelSelectedStrokeWidth = [JXCategoryFactory interpolationFrom:self.titleLabelSelectedStrokeWidth to:0 percent:ratio];
rightModel.titleLabelSelectedStrokeWidth = [JXCategoryFactory interpolationFrom:0 to:self.titleLabelSelectedStrokeWidth percent:ratio];
}
if (self.titleColorGradientEnabled) {
//处理颜色渐变
if (leftModel.selected) {
leftModel.titleSelectedColor = [JXCategoryFactory interpolationColorFrom:self.titleSelectedColor to:self.titleColor percent:ratio];
leftModel.titleColor = self.titleColor;
}else {
leftModel.titleColor = [JXCategoryFactory interpolationColorFrom:self.titleSelectedColor to:self.titleColor percent:ratio];
leftModel.titleSelectedColor = self.titleSelectedColor;
}
if (rightModel.selected) {
rightModel.titleSelectedColor = [JXCategoryFactory interpolationColorFrom:self.titleColor to:self.titleSelectedColor percent:ratio];
rightModel.titleColor = self.titleColor;
}else {
rightModel.titleColor = [JXCategoryFactory interpolationColorFrom:self.titleColor to:self.titleSelectedColor percent:ratio];
rightModel.titleSelectedColor = self.titleSelectedColor;
}
}
}
- (CGFloat)preferredCellWidthAtIndex:(NSInteger)index {
if (self.cellWidth == JXCategoryViewAutomaticDimension) {
return ceilf([self.titles[index] boundingRectWithSize:CGSizeMake(MAXFLOAT, self.bounds.size.height) options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading attributes:@{NSFontAttributeName : self.titleFont} context:nil].size.width);
}else {
return self.cellWidth;
}
}
- (void)refreshCellModel:(JXCategoryBaseCellModel *)cellModel index:(NSInteger)index {
[super refreshCellModel:cellModel index:index];
JXCategoryTitleCellModel *model = (JXCategoryTitleCellModel *)cellModel;
model.titleFont = self.titleFont;
model.titleSelectedFont = self.titleSelectedFont;
model.titleColor = self.titleColor;
model.titleSelectedColor = self.titleSelectedColor;
model.title = self.titles[index];
model.titleLabelMaskEnabled = self.titleLabelMaskEnabled;
model.titleLabelZoomEnabled = self.titleLabelZoomEnabled;
model.titleLabelZoomScale = 1.0;
model.titleLabelStrokeWidthEnabled = self.titleLabelStrokeWidthEnabled;
model.titleLabelSelectedStrokeWidth = 0;
if (index == self.selectedIndex) {
model.titleLabelZoomScale = self.titleLabelZoomScale;
model.titleLabelSelectedStrokeWidth = self.titleLabelSelectedStrokeWidth;
}
}
@end
//
// JXCategoryImageCell.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/8.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryTitleCell.h"
@interface JXCategoryTitleImageCell : JXCategoryTitleCell
@property (nonatomic, strong) UIImageView *imageView;
@end
//
// JXCategoryImageCell.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/8.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryTitleImageCell.h"
#import "JXCategoryTitleImageCellModel.h"
@interface JXCategoryTitleImageCell()
@end
@implementation JXCategoryTitleImageCell
- (void)initializeViews {
[super initializeViews];
_imageView = [[UIImageView alloc] init];
_imageView.contentMode = UIViewContentModeScaleAspectFit;
[self.contentView addSubview:_imageView];
}
- (void)layoutSubviews {
[super layoutSubviews];
JXCategoryTitleImageCellModel *myCellModel = (JXCategoryTitleImageCellModel *)self.cellModel;
self.titleLabel.hidden = NO;
self.imageView.hidden = NO;
CGSize imageSize = myCellModel.imageSize;
self.imageView.bounds = CGRectMake(0, 0, imageSize.width, imageSize.height);
switch (myCellModel.imageType) {
case JXCategoryTitleImageType_TopImage:
{
CGFloat contentHeight = imageSize.height + myCellModel.titleImageSpacing + self.titleLabel.bounds.size.height;
self.imageView.center = CGPointMake(self.contentView.center.x, (self.contentView.bounds.size.height - contentHeight)/2 + imageSize.height/2);
self.titleLabel.center = CGPointMake(self.contentView.center.x, CGRectGetMaxY(self.imageView.frame) + myCellModel.titleImageSpacing + self.titleLabel.bounds.size.height/2);
}
break;
case JXCategoryTitleImageType_LeftImage:
{
CGFloat contentWidth = imageSize.width + myCellModel.titleImageSpacing + self.titleLabel.bounds.size.width;
self.imageView.center = CGPointMake((self.contentView.bounds.size.width - contentWidth)/2 + imageSize.width/2, self.contentView.center.y);
self.titleLabel.center = CGPointMake(CGRectGetMaxX(self.imageView.frame) + myCellModel.titleImageSpacing + self.titleLabel.bounds.size.width/2, self.contentView.center.y);
}
break;
case JXCategoryTitleImageType_BottomImage:
{
CGFloat contentHeight = imageSize.height + myCellModel.titleImageSpacing + self.titleLabel.bounds.size.height;
self.titleLabel.center = CGPointMake(self.contentView.center.x, (self.contentView.bounds.size.height - contentHeight)/2 + self.titleLabel.bounds.size.height/2);
self.imageView.center = CGPointMake(self.contentView.center.x, CGRectGetMaxY(self.titleLabel.frame) + myCellModel.titleImageSpacing + imageSize.height/2);
}
break;
case JXCategoryTitleImageType_RightImage:
{
CGFloat contentWidth = imageSize.width + myCellModel.titleImageSpacing + self.titleLabel.bounds.size.width;
self.titleLabel.center = CGPointMake((self.contentView.bounds.size.width - contentWidth)/2 + self.titleLabel.bounds.size.width/2, self.contentView.center.y);
self.imageView.center = CGPointMake(CGRectGetMaxX(self.titleLabel.frame) + myCellModel.titleImageSpacing + imageSize.width/2, self.contentView.center.y);
}
break;
case JXCategoryTitleImageType_OnlyImage:
{
self.titleLabel.hidden = YES;
self.imageView.center = self.contentView.center;
}
break;
case JXCategoryTitleImageType_OnlyTitle:
{
self.imageView.hidden = YES;
self.titleLabel.center = self.contentView.center;
}
break;
default:
break;
}
}
- (void)reloadData:(JXCategoryBaseCellModel *)cellModel {
[super reloadData:cellModel];
JXCategoryTitleImageCellModel *myCellModel = (JXCategoryTitleImageCellModel *)cellModel;
if (myCellModel.imageName != nil) {
self.imageView.image = [UIImage imageNamed:myCellModel.imageName];
}else if (myCellModel.imageURL != nil) {
if (myCellModel.loadImageCallback != nil) {
myCellModel.loadImageCallback(self.imageView, myCellModel.imageURL);
}
}
if (myCellModel.selected) {
if (myCellModel.selectedImageName != nil) {
self.imageView.image = [UIImage imageNamed:myCellModel.selectedImageName];
}else if (myCellModel.selectedImageURL != nil) {
if (myCellModel.loadImageCallback != nil) {
myCellModel.loadImageCallback(self.imageView, myCellModel.selectedImageURL);
}
}
}
if (myCellModel.imageZoomEnabled) {
self.imageView.transform = CGAffineTransformMakeScale(myCellModel.imageZoomScale, myCellModel.imageZoomScale);
}else {
self.imageView.transform = CGAffineTransformIdentity;
}
[self setNeedsLayout];
[self layoutIfNeeded];
}
@end
//
// JXCategoryTitleImageCellModel.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/8.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryTitleCellModel.h"
typedef NS_ENUM(NSUInteger, JXCategoryTitleImageType) {
JXCategoryTitleImageType_TopImage = 0,
JXCategoryTitleImageType_LeftImage,
JXCategoryTitleImageType_BottomImage,
JXCategoryTitleImageType_RightImage,
JXCategoryTitleImageType_OnlyImage,
JXCategoryTitleImageType_OnlyTitle,
};
@interface JXCategoryTitleImageCellModel : JXCategoryTitleCellModel
@property (nonatomic, assign) JXCategoryTitleImageType imageType;
@property (nonatomic, copy) void(^loadImageCallback)(UIImageView *imageView, NSURL *imageURL);
@property (nonatomic, copy) NSString *imageName; //加载bundle内的图片
@property (nonatomic, strong) NSURL *imageURL; //图片URL
@property (nonatomic, copy) NSString *selectedImageName;
@property (nonatomic, strong) NSURL *selectedImageURL;
@property (nonatomic, assign) CGSize imageSize; //默认CGSizeMake(20, 20)
@property (nonatomic, assign) CGFloat titleImageSpacing; //titleLabel和ImageView的间距,默认5
@property (nonatomic, assign) BOOL imageZoomEnabled;
@property (nonatomic, assign) CGFloat imageZoomScale;
@end
//
// JXCategoryTitleImageCellModel.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/8.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryTitleImageCellModel.h"
@implementation JXCategoryTitleImageCellModel
@end
//
// JXCategoryImageView.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/8.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryTitleView.h"
#import "JXCategoryTitleImageCell.h"
#import "JXCategoryTitleImageCellModel.h"
@interface JXCategoryTitleImageView : JXCategoryTitleView
@property (nonatomic, strong) NSArray <NSString *>*imageNames;
@property (nonatomic, strong) NSArray <NSURL *>*imageURLs;
@property (nonatomic, strong) NSArray <NSString *>*selectedImageNames;
@property (nonatomic, strong) NSArray <NSURL *>*selectedImageURLs;
@property (nonatomic, strong) NSArray <NSNumber *> *imageTypes; //默认JXCategoryTitleImageType_LeftImage
@property (nonatomic, copy) void(^loadImageCallback)(UIImageView *imageView, NSURL *imageURL); //使用imageURL从远端下载图片进行加载,建议使用SDWebImage等第三方库进行下载。
@property (nonatomic, assign) CGSize imageSize; //默认CGSizeMake(20, 20)
@property (nonatomic, assign) CGFloat titleImageSpacing; //titleLabel和ImageView的间距,默认5
@property (nonatomic, assign) BOOL imageZoomEnabled; //默认为NO
@property (nonatomic, assign) CGFloat imageZoomScale; //默认1.2,imageZoomEnabled为YES才生效
@end
//
// JXCategoryImageView.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/8.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryTitleImageView.h"
#import "JXCategoryTitleImageCell.h"
#import "JXCategoryTitleImageCellModel.h"
#import "JXCategoryFactory.h"
@implementation JXCategoryTitleImageView
- (void)dealloc
{
self.loadImageCallback = nil;
}
- (void)initializeData {
[super initializeData];
_imageSize = CGSizeMake(20, 20);
_titleImageSpacing = 5;
_imageZoomEnabled = NO;
_imageZoomScale = 1.2;
}
- (Class)preferredCellClass {
return [JXCategoryTitleImageCell class];
}
- (void)refreshDataSource {
NSMutableArray *tempArray = [NSMutableArray array];
for (int i = 0; i < self.titles.count; i++) {
JXCategoryTitleImageCellModel *cellModel = [[JXCategoryTitleImageCellModel alloc] init];
[tempArray addObject:cellModel];
}
if (self.imageTypes == nil || self.imageTypes.count == 0) {
NSMutableArray *types = [NSMutableArray array];
for (int i = 0; i < self.titles.count; i++) {
[types addObject:@(JXCategoryTitleImageType_LeftImage)];
}
self.imageTypes = types;
}
self.dataSource = tempArray;
}
- (void)refreshCellModel:(JXCategoryBaseCellModel *)cellModel index:(NSInteger)index {
[super refreshCellModel:cellModel index:index];
JXCategoryTitleImageCellModel *myCellModel = (JXCategoryTitleImageCellModel *)cellModel;
myCellModel.loadImageCallback = self.loadImageCallback;
myCellModel.imageType = [self.imageTypes[index] integerValue];
myCellModel.imageSize = self.imageSize;
myCellModel.titleImageSpacing = self.titleImageSpacing;
if (self.imageNames != nil) {
myCellModel.imageName = self.imageNames[index];
}else if (self.imageURLs != nil) {
myCellModel.imageURL = self.imageURLs[index];
}
if (self.selectedImageNames != nil) {
myCellModel.selectedImageName = self.selectedImageNames[index];
}else if (self.selectedImageURLs != nil) {
myCellModel.selectedImageURL = self.selectedImageURLs[index];
}
myCellModel.imageZoomEnabled = self.imageZoomEnabled;
myCellModel.imageZoomScale = 1.0;
if (index == self.selectedIndex) {
myCellModel.imageZoomScale = self.imageZoomScale;
}
}
- (void)refreshSelectedCellModel:(JXCategoryBaseCellModel *)selectedCellModel unselectedCellModel:(JXCategoryBaseCellModel *)unselectedCellModel {
[super refreshSelectedCellModel:selectedCellModel unselectedCellModel:unselectedCellModel];
JXCategoryTitleImageCellModel *myUnselectedCellModel = (JXCategoryTitleImageCellModel *)unselectedCellModel;
myUnselectedCellModel.imageZoomScale = 1.0;
JXCategoryTitleImageCellModel *myselectedCellModel = (JXCategoryTitleImageCellModel *)selectedCellModel;
myselectedCellModel.imageZoomScale = self.imageZoomScale;
}
- (void)refreshLeftCellModel:(JXCategoryBaseCellModel *)leftCellModel rightCellModel:(JXCategoryBaseCellModel *)rightCellModel ratio:(CGFloat)ratio {
[super refreshLeftCellModel:leftCellModel rightCellModel:rightCellModel ratio:ratio];
JXCategoryTitleImageCellModel *leftModel = (JXCategoryTitleImageCellModel *)leftCellModel;
JXCategoryTitleImageCellModel *rightModel = (JXCategoryTitleImageCellModel *)rightCellModel;
if (self.imageZoomEnabled) {
leftModel.imageZoomScale = [JXCategoryFactory interpolationFrom:self.imageZoomScale to:1.0 percent:ratio];
rightModel.imageZoomScale = [JXCategoryFactory interpolationFrom:1.0 to:self.imageZoomScale percent:ratio];
}
}
- (CGFloat)preferredCellWidthAtIndex:(NSInteger)index {
CGFloat titleWidth = [super preferredCellWidthAtIndex:index];
JXCategoryTitleImageType type = [self.imageTypes[index] integerValue];
CGFloat cellWidth = 0;
switch (type) {
case JXCategoryTitleImageType_OnlyTitle:
cellWidth = titleWidth;
break;
case JXCategoryTitleImageType_OnlyImage:
cellWidth = self.imageSize.width;
break;
case JXCategoryTitleImageType_LeftImage:
case JXCategoryTitleImageType_RightImage:
cellWidth = titleWidth + self.titleImageSpacing + self.imageSize.width;
break;
case JXCategoryTitleImageType_TopImage:
case JXCategoryTitleImageType_BottomImage:
cellWidth = MAX(titleWidth, self.imageSize.width);
break;
}
return cellWidth;
}
@end
//
// JXPagerCollectionViewCell.h
// Gengmei
//
// Created by 卢悦明 on 2019/5/15.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface JXPagerCollectionViewCell : UICollectionViewCell
@property (nonatomic, strong) UIView *backView;
@end
NS_ASSUME_NONNULL_END
//
// JXPagerCollectionViewCell.m
// Gengmei
//
// Created by 卢悦明 on 2019/5/15.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "JXPagerCollectionViewCell.h"
@implementation JXPagerCollectionViewCell
- (void)setBackView:(UIView *)backView
{
if (backView!=_backView) {
[_backView removeFromSuperview];
_backView = backView;
[self addSubview:_backView];
}
}
- (void)layoutSubviews {
self.backView.frame = self.bounds;
}
@end
//
// JXPagingListContainerView.h
// JXPagingView
//
// Created by jiaxin on 2018/8/27.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
@class JXPagerMainTableView;
@class JXPagerListContainerView;
@class JXPagerListContainerCollectionView;
@protocol JXPagerListContainerCollectionViewGestureDelegate <NSObject>
- (BOOL)pagerListContainerCollectionViewGestureRecognizerShouldBegin:(JXPagerListContainerCollectionView *)collectionView gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer;
@end
@interface JXPagerListContainerCollectionView: UICollectionView<UIGestureRecognizerDelegate>
@property (nonatomic, assign) BOOL isNestEnabled;
@property (nonatomic, weak) id<JXPagerListContainerCollectionViewGestureDelegate> gestureDelegate;
@end
@protocol JXPagerListContainerViewDelegate <NSObject>
- (NSInteger)numberOfRowsInListContainerView:(JXPagerListContainerView *)listContainerView;
- (UIView *)listContainerView:(JXPagerListContainerView *)listContainerView listViewInRow:(NSInteger)row;
- (void)listContainerView:(JXPagerListContainerView *)listContainerView willDisplayCellAtRow:(NSInteger)row;
- (void)listContainerView:(JXPagerListContainerView *)listContainerView didScroll:(NSInteger)row;
@end
@interface JXPagerListContainerView : UIView
@property (nonatomic, strong, readonly) JXPagerListContainerCollectionView *collectionView;
@property (nonatomic, weak) id<JXPagerListContainerViewDelegate> delegate;
@property (nonatomic, weak) JXPagerMainTableView *mainTableView;
- (instancetype)initWithDelegate:(id<JXPagerListContainerViewDelegate>)delegate NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
- (void)reloadData;
@end
//
// JXPagerListContainerView.m
// JXPagerView
//
// Created by jiaxin on 2018/8/27.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXPagerListContainerView.h"
#import "JXPagerMainTableView.h"
@import GMKit;
@interface JXPagerListContainerView() <UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>
@property (nonatomic, strong) JXPagerListContainerCollectionView *collectionView;
@property (nonatomic, strong) UICollectionViewFlowLayout *layout;
@end
@implementation JXPagerListContainerView
-(instancetype)initWithDelegate:(id<JXPagerListContainerViewDelegate>)delegate {
self = [super initWithFrame:CGRectZero];
if (self) {
_delegate = delegate;
[self initializeViews];
}
return self;
}
- (void)initializeViews {
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.minimumLineSpacing = 0;
layout.minimumInteritemSpacing = 0;
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.layout = layout;
_collectionView = [[JXPagerListContainerCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:self.layout];
self.collectionView.showsHorizontalScrollIndicator = NO;
self.collectionView.showsVerticalScrollIndicator = NO;
self.collectionView.pagingEnabled = YES;
self.collectionView.scrollsToTop = NO;
self.collectionView.bounces = NO;
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cell"];
if (@available(iOS 10.0, *)) {
self.collectionView.prefetchingEnabled = NO;
}
if (@available(iOS 11.0, *)) {
self.collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
[self addSubview:self.collectionView];
}
- (void)layoutSubviews {
[super layoutSubviews];
self.collectionView.frame = self.bounds;
self.layout.itemSize = self.bounds.size;
}
- (void)reloadData {
[self.collectionView reloadData];
}
#pragma mark - UICollectionViewDataSource, UICollectionViewDelegate
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return [self.delegate numberOfRowsInListContainerView:self];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
for (UIView *view in cell.contentView.subviews) {
[view removeFromSuperview];
}
UIView *listView = [self.delegate listContainerView:self listViewInRow:indexPath.item];
listView.frame = cell.contentView.bounds;
[cell.contentView addSubview:listView];
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
[self.delegate listContainerView:self willDisplayCellAtRow:indexPath.item];
}
- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath {
return false;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
self.mainTableView.scrollEnabled = YES;
if ([self.delegate respondsToSelector:@selector(listContainerView:didScroll:)]) {
NSInteger index = (self.collectionView.contentOffset.x / MAINSCREEN_WIDTH + 0.5);
[self.delegate listContainerView:self didScroll:index];
}
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if ([self.delegate respondsToSelector:@selector(listContainerView:didScroll:)]) {
NSInteger index = (self.collectionView.contentOffset.x / MAINSCREEN_WIDTH + 0.5);
[self.delegate listContainerView:self didScroll:index];
}
self.mainTableView.scrollEnabled = YES;
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
self.mainTableView.scrollEnabled = YES;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView.isTracking || scrollView.isDecelerating) {
self.mainTableView.scrollEnabled = NO;
}
}
@end
@interface JXPagerListContainerCollectionView ()
@end
@implementation JXPagerListContainerCollectionView
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if (self.gestureDelegate) {
return [self.gestureDelegate pagerListContainerCollectionViewGestureRecognizerShouldBegin:self gestureRecognizer:gestureRecognizer];
}else {
if (self.isNestEnabled) {
if ([gestureRecognizer isMemberOfClass:NSClassFromString(@"UIScrollViewPanGestureRecognizer")]) {
CGFloat velocityX = [(UIPanGestureRecognizer *)gestureRecognizer velocityInView:gestureRecognizer.view].x;
//x大于0就是往右滑
if (velocityX > 0) {
if (self.contentOffset.x == 0) {
return NO;
}
}else if (velocityX < 0) {
//x小于0就是往左滑
if (self.contentOffset.x + self.bounds.size.width == self.contentSize.width) {
return NO;
}
}
}
}
}
return YES;
}
@end
//
// JXPagingListRefreshView.h
// JXPagingView
//
// Created by jiaxin on 2018/8/28.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "JXPagerView.h"
@interface JXPagerListRefreshView : JXPagerView
@end
//
// JXPagerListRefreshView.m
// JXPagerView
//
// Created by jiaxin on 2018/8/28.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXPagerListRefreshView.h"
@interface JXPagerListRefreshView()
@property (nonatomic, assign) CGFloat lastScrollingListViewContentOffsetY;
@end
@implementation JXPagerListRefreshView
- (void)initializeViews {
[super initializeViews];
self.mainTableView.bounces = NO;
}
- (void)preferredProcessListViewDidScroll:(UIScrollView *)scrollView {
BOOL shouldProcess = YES;
if (self.currentScrollingListView.contentOffset.y > self.lastScrollingListViewContentOffsetY) {
//往上滚动
}else {
//往下滚动
if (self.mainTableView.contentOffset.y == 0) {
shouldProcess = NO;
}else {
if (self.mainTableView.contentOffset.y < [self.delegate tableHeaderViewHeightInPagerView:self]) {
//mainTableView的header还没有消失,让listScrollView一直为0
if (self.currentList && [self.currentList respondsToSelector:@selector(listScrollViewWillResetContentOffset)]) {
[self.currentList listScrollViewWillResetContentOffset];
}
self.currentScrollingListView.contentOffset = CGPointZero;
self.currentScrollingListView.showsVerticalScrollIndicator = false;
}
}
}
if (shouldProcess) {
if (self.mainTableView.contentOffset.y < [self.delegate tableHeaderViewHeightInPagerView:self]) {
//处于下拉刷新的状态,scrollView.contentOffset.y为负数,就重置为0
if (self.currentScrollingListView.contentOffset.y > 0) {
//mainTableView的header还没有消失,让listScrollView一直为0
if (self.currentList && [self.currentList respondsToSelector:@selector(listScrollViewWillResetContentOffset)]) {
[self.currentList listScrollViewWillResetContentOffset];
}
self.currentScrollingListView.contentOffset = CGPointZero;
self.currentScrollingListView.showsVerticalScrollIndicator = false;
}
} else {
//mainTableView的header刚好消失,固定mainTableView的位置,显示listScrollView的滚动条
self.mainTableView.contentOffset = CGPointMake(0, [self.delegate tableHeaderViewHeightInPagerView:self]);
self.currentScrollingListView.showsVerticalScrollIndicator = true;
}
}
self.lastScrollingListViewContentOffsetY = self.currentScrollingListView.contentOffset.y;
}
- (void)preferredProcessMainTableViewDidScroll:(UIScrollView *)scrollView {
if (self.currentScrollingListView != nil && self.currentScrollingListView.contentOffset.y > 0) {
//mainTableView的header已经滚动不见,开始滚动某一个listView,那么固定mainTableView的contentOffset,让其不动
self.mainTableView.contentOffset = CGPointMake(0, [self.delegate tableHeaderViewHeightInPagerView:self]);
}
if (scrollView.contentOffset.y < [self.delegate tableHeaderViewHeightInPagerView:self]) {
//mainTableView已经显示了header,listView的contentOffset需要重置
for (id<JXPagerViewListViewDelegate> list in self.validListDict.allValues) {
//正在下拉刷新时,不需要重置
UIScrollView *listScrollView = [list listScrollView];
if (listScrollView.contentOffset.y > 0) {
if ([list respondsToSelector:@selector(listScrollViewWillResetContentOffset)]) {
[list listScrollViewWillResetContentOffset];
}
listScrollView.contentOffset = CGPointZero;
}
}
}
}
@end
//
// JXPagingMainTableView.h
// JXPagingView
//
// Created by jiaxin on 2018/8/27.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
@protocol JXPagerMainTableViewGestureDelegate <NSObject>
- (BOOL)mainTableViewGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;
@end
@interface JXPagerMainTableView : UITableView
@property (nonatomic, weak) id<JXPagerMainTableViewGestureDelegate> gestureDelegate;
@end
//
// JXPagerMainTableView.m
// JXPagerView
//
// Created by jiaxin on 2018/8/27.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXPagerMainTableView.h"
@interface JXPagerMainTableView ()<UIGestureRecognizerDelegate>
@end
@implementation JXPagerMainTableView
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
if (self.gestureDelegate && [self.gestureDelegate respondsToSelector:@selector(mainTableViewGestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)]) {
return [self.gestureDelegate mainTableViewGestureRecognizer:gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:otherGestureRecognizer];
}else {
return [gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] && [otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]];
}
}
@end
//
// JXPagerView.h
// JXPagerView
//
// Created by jiaxin on 2018/8/27.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "JXPagerMainTableView.h"
#import "JXPagerListContainerView.h"
@class JXPagerView;
/**
该协议主要用于mainTableView已经显示了header,listView的contentOffset需要重置时,内部需要访问到外部传入进来的listView内的scrollView
*/
@protocol JXPagerViewListViewDelegate <NSObject>
/**
返回listView。如果是vc包裹的就是vc.view;如果是自定义view包裹的,就是自定义view自己。
@return UIView
*/
- (UIView *)listView;
/**
返回listView内部持有的UIScrollView或UITableView或UICollectionView
主要用于mainTableView已经显示了header,listView的contentOffset需要重置时,内部需要访问到外部传入进来的listView内的scrollView
@return listView内部持有的UIScrollView或UITableView或UICollectionView
*/
- (UIScrollView *)listScrollView;
/**
当listView内部持有的UIScrollView或UITableView或UICollectionView的代理方法`scrollViewDidScroll`回调时,需要调用该代理方法传入的callback
@param callback `scrollViewDidScroll`回调时调用的callback
*/
- (void)listViewDidScrollCallback:(void (^)(UIScrollView *scrollView))callback;
@optional
/**
将要重置listScrollView的contentOffset
*/
- (void)listScrollViewWillResetContentOffset;
@end
@protocol JXPagerViewDelegate <NSObject>
/**
返回tableHeaderView的高度,因为内部需要比对判断,只能是整型数
@param pagerView pagerView description
@return return tableHeaderView的高度
*/
- (NSUInteger)tableHeaderViewHeightInPagerView:(JXPagerView *)pagerView;
/**
返回tableHeaderView
@param pagerView pagerView description
@return tableHeaderView
*/
- (UIView *)tableHeaderViewInPagerView:(JXPagerView *)pagerView;
/**
返回悬浮HeaderView的高度,因为内部需要比对判断,只能是整型数
@param pagerView pagerView description
@return 悬浮HeaderView的高度
*/
- (NSUInteger)heightForPinSectionHeaderInPagerView:(JXPagerView *)pagerView;
/**
返回悬浮HeaderView。我用的是自己封装的JXCategoryView(Github:https://github.com/pujiaxin33/JXCategoryView),你也可以选择其他的三方库或者自己写
@param pagerView pagerView description
@return 悬浮HeaderView
*/
- (UIView *)viewForPinSectionHeaderInPagerView:(JXPagerView *)pagerView;
/**
返回列表的数量
@param pagerView pagerView description
@return 列表的数量
*/
- (NSInteger)numberOfListsInPagerView:(JXPagerView *)pagerView;
/**
根据index初始化一个对应列表实例。注意:一定要是新生成的实例!!!
只要遵循JXPagerViewListViewDelegate即可,无论你返回的是UIView还是UIViewController都可以。
@param pagerView pagerView description
@param index index description
@return 新生成的列表实例
*/
- (id<JXPagerViewListViewDelegate>)pagerView:(JXPagerView *)pagerView initListAtIndex:(NSInteger)index;
- (void)pagerView:(JXPagerView *)pagerView didScroll:(NSInteger)row;
@optional
/**
返回tableHeaderView 距离pagerView顶部的距离 默认为 0
*/
- (CGFloat)tableViewSectionHeaderViewContentYInPagerView:(JXPagerView *)pagerView;
/**
mainTableView的滚动回调,用于实现头图跟随缩放
@param scrollView mainTableView
*/
- (void)mainTableViewDidScroll:(UIScrollView *)scrollView;
- (void)mainTableViewDidEndDecelerating:(UIScrollView *)scrollView;
- (void)mainTableViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;
- (void)mainTableViewWillBeginDragging:(UIScrollView *)scrollView;
/**
@param 由于 mainTableViewDidScroll 滚动偏移量返回的最大值为headerView的高度, 这个偏移量加上子ScrollView的偏移量。
*/
- (void)listAndMainScrollViewDidScroll:(CGFloat)contentOffsetY;
@end
@interface JXPagerView : UIView
@property (nonatomic, weak) id<JXPagerViewDelegate> delegate;
@property (nonatomic, strong) JXPagerMainTableView *mainTableView;
@property (nonatomic, strong, readonly) JXPagerListContainerView *listContainerView;
- (instancetype)initWithDelegate:(id<JXPagerViewDelegate>)delegate NS_DESIGNATED_INITIALIZER;
@property (nonatomic, assign) BOOL isListHorizontalScrollEnabled; //是否允许列表左右滑动。默认:YES
@property (nonatomic, assign, readonly) CGFloat mainViewScrollOffsetY;
@property (nonatomic, assign, readonly) BOOL isListScroll;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
- (void)initializeViews NS_REQUIRES_SUPER;
- (void)reloadData;
- (void)refreshTableHeaderView;
#pragma mark - Subclass
@property (nonatomic, strong, readonly) UIScrollView *currentScrollingListView; //暴露给子类使用,请勿直接使用该属性!
@property (nonatomic, weak, readonly) id<JXPagerViewListViewDelegate> currentList; //暴露给子类使用,请勿直接使用该属性!
@property (nonatomic, strong, readonly) NSDictionary <NSNumber *, id<JXPagerViewListViewDelegate>> *validListDict; //暴露给子类使用,请勿直接使用该属性!当前已经加载过可用的列表字典,key就是index值,value是对应的列表。
- (void)preferredProcessListViewDidScroll:(UIScrollView *)scrollView;
- (void)preferredProcessMainTableViewDidScroll:(UIScrollView *)scrollView;
@end
//
// JXPagerView.m
// JXPagerView
//
// Created by jiaxin on 2018/8/27.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXPagerView.h"
@import GMRefresh;
@interface JXPagerView () <UITableViewDataSource, UITableViewDelegate, JXPagerListContainerViewDelegate>
//@property (nonatomic, strong) JXPagerMainTableView *mainTableView;
@property (nonatomic, strong) JXPagerListContainerView *listContainerView;
@property (nonatomic, strong) UIScrollView *currentScrollingListView;
@property (nonatomic, weak) id<JXPagerViewListViewDelegate> currentList;
@property (nonatomic, strong) NSMutableDictionary <NSNumber *, id<JXPagerViewListViewDelegate>> *validListDict;
@property (nonatomic, assign) CGFloat mainViewScrollOffsetY;
@property (nonatomic, assign) BOOL isListScroll;
@end
@implementation JXPagerView
- (instancetype)initWithDelegate:(id<JXPagerViewDelegate>)delegate {
self = [super initWithFrame:CGRectZero];
if (self) {
_delegate = delegate;
_validListDict = [NSMutableDictionary dictionary];
[self initializeViews];
}
return self;
}
- (void)initializeViews {
_mainTableView = [[JXPagerMainTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
self.mainTableView.showsVerticalScrollIndicator = NO;
self.mainTableView.showsHorizontalScrollIndicator = NO;
self.mainTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.mainTableView.scrollsToTop = NO;
self.mainTableView.dataSource = self;
self.mainTableView.delegate = self;
[self refreshTableHeaderView];
[self.mainTableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"];
if (@available(iOS 11.0, *)) {
self.mainTableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
[self addSubview:self.mainTableView];
_listContainerView = [[JXPagerListContainerView alloc] initWithDelegate:self];
self.listContainerView.mainTableView = self.mainTableView;
self.isListHorizontalScrollEnabled = YES;
}
- (void)layoutSubviews {
[super layoutSubviews];
self.mainTableView.frame = self.bounds;
}
- (void)setIsListHorizontalScrollEnabled:(BOOL)isListHorizontalScrollEnabled {
_isListHorizontalScrollEnabled = isListHorizontalScrollEnabled;
self.listContainerView.collectionView.scrollEnabled = isListHorizontalScrollEnabled;
}
- (void)reloadData {
self.currentList = nil;
self.currentScrollingListView = nil;
for (id<JXPagerViewListViewDelegate> list in self.validListDict.allValues) {
[list.listView removeFromSuperview];
}
[_validListDict removeAllObjects];
[self refreshTableHeaderView];
[self.mainTableView reloadData];
[self.listContainerView reloadData];
}
- (void)refreshTableHeaderView {
UIView *tableHeaderView = [self.delegate tableHeaderViewInPagerView:self];
// UIView *containerView = [[UIView alloc] initWithFrame:tableHeaderView.bounds];
// [containerView addSubview:tableHeaderView];
self.mainTableView.tableHeaderView = tableHeaderView;
}
- (void)preferredProcessListViewDidScroll:(UIScrollView *)scrollView {
CGFloat listScrollOffsetY = scrollView.contentOffset.y;
if ([self.delegate respondsToSelector:@selector(listAndMainScrollViewDidScroll:)]) {
[self.delegate listAndMainScrollViewDidScroll:(listScrollOffsetY + self.mainViewScrollOffsetY)];
}
if (self.mainTableView.contentOffset.y < [self.delegate tableHeaderViewHeightInPagerView:self]) {
//mainTableView的header还没有消失,让listScrollView一直为0
if (self.currentList && [self.currentList respondsToSelector:@selector(listScrollViewWillResetContentOffset)]) {
[self.currentList listScrollViewWillResetContentOffset];
}
scrollView.contentOffset = CGPointZero;
scrollView.showsVerticalScrollIndicator = NO;
}else {
//mainTableView的header刚好消失,固定mainTableView的位置,显示listScrollView的滚动条
self.mainTableView.contentOffset = CGPointMake(0, [self.delegate tableHeaderViewHeightInPagerView:self]);
scrollView.showsVerticalScrollIndicator = YES;
}
}
- (void)preferredProcessMainTableViewDidScroll:(UIScrollView *)scrollView {
if (self.currentScrollingListView != nil && self.currentScrollingListView.contentOffset.y > 0) {
//mainTableView的header已经滚动不见,开始滚动某一个listView,那么固定mainTableView的contentOffset,让其不动
self.mainTableView.contentOffset = CGPointMake(0, [self.delegate tableHeaderViewHeightInPagerView:self]);
}
if (scrollView.contentOffset.y < [self.delegate tableHeaderViewHeightInPagerView:self]) {
//mainTableView已经显示了header,listView的contentOffset需要重置
for (id<JXPagerViewListViewDelegate> list in self.validListDict.allValues) {
if ([list respondsToSelector:@selector(listScrollViewWillResetContentOffset)]) {
[list listScrollViewWillResetContentOffset];
}
[list listScrollView].contentOffset = CGPointZero;
}
}
if (scrollView.contentOffset.y > [self.delegate tableHeaderViewHeightInPagerView:self] && self.currentScrollingListView.contentOffset.y == 0) {
//当往上滚动mainTableView的headerView时,滚动到底时,修复listView往上小幅度滚动
self.mainTableView.contentOffset = CGPointMake(0, [self.delegate tableHeaderViewHeightInPagerView:self]);
}
}
#pragma mark - Private
- (void)listViewDidScroll:(UIScrollView *)scrollView {
self.currentScrollingListView = scrollView;
[self preferredProcessListViewDidScroll:scrollView];
}
#pragma mark - UITableViewDataSource, UITableViewDelegate
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return self.bounds.size.height - [self.delegate heightForPinSectionHeaderInPagerView:self];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
for (UIView *view in cell.contentView.subviews) {
[view removeFromSuperview];
}
self.listContainerView.frame = cell.contentView.bounds;
[cell.contentView addSubview:self.listContainerView];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return [self.delegate heightForPinSectionHeaderInPagerView:self];
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
return [self.delegate viewForPinSectionHeaderInPagerView:self];
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
return 1;
}
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
UIView *footer = [[UIView alloc] initWithFrame:CGRectZero];
footer.backgroundColor = [UIColor clearColor];
return footer;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (self.mainTableView.mj_header.refreshing) {
return;
}
CGFloat sectionHeaderHeight = [self.delegate heightForPinSectionHeaderInPagerView:self];
CGFloat sectionHeaderContentY = 0;
if ([self.delegate respondsToSelector:@selector(tableViewSectionHeaderViewContentYInPagerView:)]) {
sectionHeaderContentY = [self.delegate tableViewSectionHeaderViewContentYInPagerView:self];
}
CGFloat ceilPositon = [self.delegate tableHeaderViewHeightInPagerView:self] -sectionHeaderHeight - sectionHeaderContentY;
if (scrollView.contentOffset.y < sectionHeaderHeight) {
scrollView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0);
}
if (scrollView.contentOffset.y>=ceilPositon && scrollView.contentOffset.y>=sectionHeaderHeight) {
scrollView.contentInset = UIEdgeInsetsMake(sectionHeaderContentY, 0, 0, 0);
}
CGFloat offsetY = scrollView.contentOffset.y;
self.mainViewScrollOffsetY = offsetY;
if ([self.delegate respondsToSelector:@selector(mainTableViewDidScroll:)]) {
[self.delegate mainTableViewDidScroll:scrollView];
}
if (scrollView.isTracking && self.isListHorizontalScrollEnabled) {
self.listContainerView.collectionView.scrollEnabled = NO;
}
[self preferredProcessMainTableViewDidScroll:scrollView];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
if (self.isListHorizontalScrollEnabled) {
self.listContainerView.collectionView.scrollEnabled = YES;
}
if ([self.delegate respondsToSelector:@selector(mainTableViewDidEndDecelerating:)]) {
if (_isListScroll) {
[self.delegate mainTableViewDidEndDecelerating:self.currentScrollingListView];
} else {
[self.delegate mainTableViewDidEndDecelerating:scrollView];
}
}
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (self.isListHorizontalScrollEnabled) {
self.listContainerView.collectionView.scrollEnabled = YES;
}
if ([self.delegate respondsToSelector:@selector(mainTableViewDidEndDragging:willDecelerate:)]) {
if (_isListScroll) {
[self.delegate mainTableViewDidEndDragging:self.currentScrollingListView willDecelerate:decelerate];
} else {
[self.delegate mainTableViewDidEndDragging:scrollView willDecelerate:decelerate];
}
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
if ([self.delegate respondsToSelector:@selector(mainTableViewWillBeginDragging:)]) {
if (self.currentScrollingListView != nil && self.currentScrollingListView.contentOffset.y > 0) {
_isListScroll = YES; // 为了前后判断滑动的view是否一致
[self.delegate mainTableViewWillBeginDragging:self.currentScrollingListView];
} else {
_isListScroll = NO;
[self.delegate mainTableViewWillBeginDragging:scrollView];
}
}
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
if (self.isListHorizontalScrollEnabled) {
self.listContainerView.collectionView.scrollEnabled = YES;
}
}
#pragma mark - JXPagingListContainerViewDelegate
- (NSInteger)numberOfRowsInListContainerView:(JXPagerListContainerView *)listContainerView {
return [self.delegate numberOfListsInPagerView:self];
}
- (UIView *)listContainerView:(JXPagerListContainerView *)listContainerView listViewInRow:(NSInteger)row {
id<JXPagerViewListViewDelegate> list = self.validListDict[@(row)];
if (list == nil) {
list = [self.delegate pagerView:self initListAtIndex:row];
__weak typeof(self)weakSelf = self;
__weak typeof(id<JXPagerViewListViewDelegate>) weakList = list;
[list listViewDidScrollCallback:^(UIScrollView *scrollView) {
weakSelf.currentList = weakList;
[weakSelf listViewDidScroll:scrollView];
}];
_validListDict[@(row)] = list;
}
return [list listView];
}
- (void)listContainerView:(JXPagerListContainerView *)listContainerView willDisplayCellAtRow:(NSInteger)row {
self.currentScrollingListView = [self.validListDict[@(row)] listScrollView];
}
- (void)listContainerView:(JXPagerListContainerView *)listContainerView didScroll:(NSInteger)row {
if ([self.delegate respondsToSelector:@selector(pagerView:didScroll:)]) {
[self.delegate pagerView:self didScroll:row];
}
}
@end
//
// JXPagingListContainerView.h
// JXPagingView
//
// Created by jiaxin on 2018/8/27.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
@class JXPageListMainTableView;
@class JXPageListContainerView;
@protocol JXPageListContainerViewDelegate <NSObject>
- (NSInteger)numberOfRowsInListContainerView:(JXPageListContainerView *)listContainerView;
- (UIView *)listContainerView:(JXPageListContainerView *)listContainerView listViewInRow:(NSInteger)row;
- (void)listContainerView:(JXPageListContainerView *)listContainerView willDisplayCellAtRow:(NSInteger)row;
@end
@interface JXPageListContainerView : UIView
@property (nonatomic, strong, readonly) UICollectionView *collectionView;
@property (nonatomic, weak) id<JXPageListContainerViewDelegate> delegate;
@property (nonatomic, weak) JXPageListMainTableView *mainTableView;
- (instancetype)initWithDelegate:(id<JXPageListContainerViewDelegate>)delegate NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
- (void)reloadData;
@end
//
// JXPagerListContainerView.m
//
// Created by jiaxin on 2018/8/27.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXPageListContainerView.h"
#import "JXPageListMainTableView.h"
@interface JXPageListContainerView() <UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>
@property (nonatomic, strong) UICollectionView *collectionView;
@end
@implementation JXPageListContainerView
- (instancetype)initWithDelegate:(id<JXPageListContainerViewDelegate>)delegate {
self = [super initWithFrame:CGRectZero];
if (self) {
_delegate = delegate;
[self initializeViews];
}
return self;
}
- (void)initializeViews {
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.minimumLineSpacing = 0;
layout.minimumInteritemSpacing = 0;
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
self.collectionView.showsHorizontalScrollIndicator = NO;
self.collectionView.showsVerticalScrollIndicator = NO;
self.collectionView.pagingEnabled = YES;
self.collectionView.bounces = NO;
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cell"];
[self addSubview:self.collectionView];
}
- (void)layoutSubviews {
[super layoutSubviews];
self.collectionView.frame = self.bounds;
}
- (void)reloadData {
[self.collectionView reloadData];
}
#pragma mark - UICollectionViewDataSource, UICollectionViewDelegate
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return [self.delegate numberOfRowsInListContainerView:self];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
for (UIView *view in cell.contentView.subviews) {
[view removeFromSuperview];
}
UIView *listView = [self.delegate listContainerView:self listViewInRow:indexPath.item];
listView.frame = cell.contentView.bounds;
[cell.contentView addSubview:listView];
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
[self.delegate listContainerView:self willDisplayCellAtRow:indexPath.item];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
self.mainTableView.scrollEnabled = YES;
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
self.mainTableView.scrollEnabled = YES;
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
self.mainTableView.scrollEnabled = YES;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView.isTracking || scrollView.isDecelerating) {
self.mainTableView.scrollEnabled = NO;
}
}
#pragma mark - UICollectionViewDelegateFlowLayout
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return self.bounds.size;
}
@end
//
// JXPagingMainTableView.h
// JXPagingView
//
// Created by jiaxin on 2018/8/27.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface JXPageListMainTableView : UITableView
@end
//
// JXPagerMainTableView.m
//
// Created by jiaxin on 2018/8/27.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXPageListMainTableView.h"
@interface JXPageListMainTableView ()<UIGestureRecognizerDelegate>
@end
@implementation JXPageListMainTableView
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return [gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] && [otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]];
}
@end
//
// JXPageView.h
//
// Created by jiaxin on 2018/9/13.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "JXPageListMainTableView.h"
#import "JXPageListContainerView.h"
#import "JXCategoryView.h"
@class JXPageListView;
@protocol JXPageListViewListDelegate <NSObject>
/**
返回listView内部持有的UIScrollView或UITableView或UICollectionView
主要用于mainTableView已经显示了顶部内容,listView的contentOffset需要重置时,内部需要访问到外部传入进来的listView内的scrollView
@return listView内部持有的UIScrollView或UITableView或UICollectionView
*/
- (UIScrollView *)listScrollView;
/**
当listView内部持有的UIScrollView或UITableView或UICollectionView的代理方法`scrollViewDidScroll`回调时,需要调用该代理方法传入的callback
@param callback `scrollViewDidScroll`回调时调用的callback
*/
- (void)listViewDidScrollCallback:(void (^)(UIScrollView *scrollView))callback;
/**
选中listView之后触发数据加载。会多次回调,所以listView内部要做个标志,第一次才触发数据加载。
*/
- (void)listViewLoadDataIfNeeded;
@end
@protocol JXPageListViewDelegate <NSObject>
/**
返回listViews,数组的item需要是UIView的子类,且要遵循JXPageListViewListDelegate。
数组item要求返回一个UIView而不是一个UIScrollView,因为列表的UIScrollView一般是被包装到一个view里面,里面会处理数据源和其他逻辑。
@param pageListView pageListView description
@return listViews
*/
- (NSArray <UIView <JXPageListViewListDelegate>*>*)listViewsInPageListView:(JXPageListView *)pageListView;
@optional
/**
悬浮分类视图选中某个index的回调
@param pinCategoryView 悬浮分类视图
@param index index
*/
- (void)pinCategoryView:(JXCategoryBaseView *)pinCategoryView didSelectedItemAtIndex:(NSInteger)index;
@end
/**
按照demo工程PageViewController里面的tips使用,把最后一个section留给listContainerCell。其余的,你就把mainTableView当做普通的tableView来处理就行。上面想添加多少cell都随你。
*/
@interface JXPageListView : UIView
@property (nonatomic, strong, readonly) JXCategoryTitleView *pinCategoryView;
@property (nonatomic, strong, readonly) JXPageListMainTableView *mainTableView;
@property (nonatomic, assign, getter=isListViewScrollStateSaveEnabled) BOOL listViewScrollStateSaveEnabled; //是否保存底部滚动列表视图的滚动状态,默认YES。设置为YES,表示A列表被滚动一定距离后,切换到B列表,当B列表往上滚动到最顶部的时候,再切换到A列表,A列表保存了它之前的滚动距离;设置为NO,表示A列表被滚动一定距离后,切换到B列表,当B列表往上滚动到最顶部的时候,再切换到A列表,A列表也同步被滚动到最顶部了;具体可以参考demo效果。
@property (nonatomic, assign) CGFloat pinCategoryViewHeight; //顶部固定悬浮categoryView的高度
@property (nonatomic, assign) CGFloat pinCategoryViewVerticalOffset; //顶部固定sectionHeader的垂直偏移量。数值越大越往下沉。
- (instancetype)initWithDelegate:(id<JXPageListViewDelegate>)delegate NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
/**
通过服务器获取到数据之后,再调用reloadData方法刷新
*/
- (void)reloadData;
/**
返回列表容器cell的高度
*/
- (CGFloat)listContainerCellHeight;
/**
返回列表容器cell
*/
- (UITableViewCell *)listContainerCellForRowAtIndexPath:(NSIndexPath *)indexPath;
/**
mainTableView的代理方法`scrollViewDidScroll`回调时需要调用该方法
*/
- (void)mainTableViewDidScroll:(UIScrollView *)scrollView;
@end
//
// JXPageView.m
//
// Created by jiaxin on 2018/9/13.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXPageListView.h"
#import "JXPageListContainerView.h"
static NSString *const kListContainerCellIdentifier = @"jx_kListContainerCellIdentifier";
@interface JXPageListView () <JXPageListContainerViewDelegate, JXCategoryViewDelegate>
@property (nonatomic, strong) JXCategoryTitleView *pinCategoryView;
@property (nonatomic, weak) id<JXPageListViewDelegate> delegate;
@property (nonatomic, strong) JXPageListMainTableView *mainTableView;
@property (nonatomic, strong) JXPageListContainerView *listContainerView;
@property (nonatomic, strong) UIScrollView *currentScrollingListView;
@property (nonatomic, strong) UITableViewCell *listContainerCell;
@property (nonatomic, strong) NSArray *originalListViews;
@end
@implementation JXPageListView
- (instancetype)initWithDelegate:(id<JXPageListViewDelegate>)delegate {
self = [super initWithFrame:CGRectZero];
if (self) {
_delegate = delegate;
[self initializeViews];
}
return self;
}
- (void)initializeViews {
_listViewScrollStateSaveEnabled = YES;
_pinCategoryViewVerticalOffset = 0;
self.pinCategoryView = [[JXCategoryTitleView alloc] initWithFrame:CGRectZero];
self.pinCategoryView.backgroundColor = [UIColor whiteColor];
self.pinCategoryView.delegate = self;
self.pinCategoryView.titleSelectedColor = [UIColor colorWithRed:105/255.0 green:144/255.0 blue:239/255.0 alpha:1];
self.pinCategoryView.titleColor = [UIColor blackColor];
self.pinCategoryView.titleColorGradientEnabled = YES;
self.pinCategoryView.titleLabelZoomEnabled = YES;
JXCategoryIndicatorLineView *lineView = [[JXCategoryIndicatorLineView alloc] init];
lineView.indicatorColor = [UIColor colorWithRed:105/255.0 green:144/255.0 blue:239/255.0 alpha:1];
lineView.indicatorWidth = 30;
self.pinCategoryView.indicators = @[lineView];
_mainTableView = [[JXPageListMainTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
self.mainTableView.showsVerticalScrollIndicator = NO;
self.mainTableView.tableFooterView = [UIView new];
[self.mainTableView registerClass:[UITableViewCell class] forCellReuseIdentifier:kListContainerCellIdentifier];
if (@available(iOS 11.0, *)) {
self.mainTableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
[self addSubview:self.mainTableView];
_listContainerView = [[JXPageListContainerView alloc] initWithDelegate:self];
self.listContainerView.mainTableView = self.mainTableView;
self.pinCategoryView.contentScrollView = self.listContainerView.collectionView;
[self configListViewDidScrollCallback];
}
- (void)layoutSubviews {
[super layoutSubviews];
self.mainTableView.frame = self.bounds;
}
- (void)reloadData {
//先移除以前的listView
for (UIView *listView in self.originalListViews) {
[listView removeFromSuperview];
}
[self configListViewDidScrollCallback];
[self.mainTableView reloadData];
[self.listContainerView reloadData];
[self.pinCategoryView reloadData];
}
- (CGFloat)listContainerCellHeight {
return self.bounds.size.height - self.pinCategoryViewVerticalOffset;
}
- (UITableViewCell *)listContainerCellForRowAtIndexPath:(NSIndexPath *)indexPath {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//触发第一个列表的下拉刷新
NSArray *listViews = [self.delegate listViewsInPageListView:self];
if (listViews.count > 0) {
[listViews[self.pinCategoryView.selectedIndex] listViewLoadDataIfNeeded];
}
});
UITableViewCell *cell = [self.mainTableView dequeueReusableCellWithIdentifier:kListContainerCellIdentifier forIndexPath:indexPath];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
self.listContainerCell = cell;
for (UIView *view in cell.contentView.subviews) {
[view removeFromSuperview];
}
self.pinCategoryView.frame = CGRectMake(0, 0, cell.contentView.bounds.size.width, self.pinCategoryViewHeight);
if (self.pinCategoryView.superview != cell.contentView) {
//首次使用pinCategoryView的时候,把pinCategoryView添加到它上面。
[cell.contentView addSubview:self.pinCategoryView];
}
self.listContainerView.frame = CGRectMake(0, self.pinCategoryViewHeight, cell.contentView.bounds.size.width, cell.contentView.bounds.size.height - self.pinCategoryViewHeight);
[cell.contentView addSubview:self.listContainerView];
return cell;
}
- (void)mainTableViewDidScroll:(UIScrollView *)scrollView {
//处理当页面滚动在最底部pageView的时候,页面需要立即停住,没有回弹效果。给用户一种切换到pageView浏览模式了
if (scrollView.contentOffset.y > 10) {
scrollView.bounces = NO;
}else {
scrollView.bounces = YES;
}
CGFloat topContentY = [self mainTableViewMaxContentOffsetY];
if (scrollView.contentOffset.y >= topContentY) {
//当滚动的contentOffset.y大于了指定sectionHeader的y值,且还没有被添加到self.view上的时候,就需要切换superView
if (self.pinCategoryView.superview != self) {
CGRect frame = self.pinCategoryView.frame;
frame.origin.y = self.pinCategoryViewVerticalOffset;
self.pinCategoryView.frame = frame;
[self addSubview:self.pinCategoryView];
}
}else if (self.pinCategoryView.superview != self.listContainerCell.contentView) {
//当滚动的contentOffset.y小于了指定sectionHeader的y值,且还没有被添加到sectionCategoryHeaderView上的时候,就需要切换superView
CGRect frame = self.pinCategoryView.frame;
frame.origin.y = 0;
self.pinCategoryView.frame = frame;
[self.listContainerCell.contentView addSubview:self.pinCategoryView];
}
if (scrollView.isTracking || scrollView.isDecelerating) {
//用户滚动的才处理
if (self.currentScrollingListView != nil && self.currentScrollingListView.contentOffset.y > 0) {
self.mainTableView.contentOffset = CGPointMake(self.mainTableView.contentOffset.x, topContentY);
}
}
if (!self.isListViewScrollStateSaveEnabled) {
if (scrollView.contentOffset.y < topContentY) {
//mainTableView已经显示了header,listView的contentOffset需要重置
NSArray *listViews = [self.delegate listViewsInPageListView:self];
CGFloat insetTop = scrollView.contentInset.top;
if (@available(iOS 11.0, *)) {
insetTop = scrollView.adjustedContentInset.top;
}
for (UIView <JXPageListViewListDelegate>* listView in listViews) {
[listView listScrollView].contentOffset = CGPointMake(self.mainTableView.contentOffset.x, -insetTop);
}
}
}
}
#pragma mark - Private
- (void)configListViewDidScrollCallback {
NSArray *listViews = [self.delegate listViewsInPageListView:self];
self.originalListViews = listViews;
for (UIView <JXPageListViewListDelegate>* listView in listViews) {
__weak typeof(self)weakSelf = self;
[listView listViewDidScrollCallback:^(UIScrollView *scrollView) {
[weakSelf listViewDidScroll:scrollView];
}];
}
}
- (void)listViewDidSelectedAtIndex:(NSInteger)index {
UIView<JXPageListViewListDelegate> *listContainerView = [self.delegate listViewsInPageListView:self][index];
if ([listContainerView listScrollView].contentOffset.y > 0) {
//当前列表视图已经滚动显示了内容
[self.mainTableView setContentOffset:CGPointMake(self.mainTableView.contentOffset.x, [self mainTableViewMaxContentOffsetY]) animated:YES];
}
}
- (CGFloat)mainTableViewMaxContentOffsetY {
return floor(self.mainTableView.contentSize.height) - self.bounds.size.height;
}
- (void)listViewDidScroll:(UIScrollView *)scrollView {
self.currentScrollingListView = scrollView;
if (scrollView.isTracking == NO && scrollView.isDecelerating == NO) {
return;
}
CGFloat topContentHeight = [self mainTableViewMaxContentOffsetY];
if (self.mainTableView.contentOffset.y < topContentHeight) {
//mainTableView的header还没有消失,让listScrollView固定
CGFloat insetTop = scrollView.contentInset.top;
if (@available(iOS 11.0, *)) {
insetTop = scrollView.adjustedContentInset.top;
}
scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x, -insetTop);
}else {
//mainTableView的header刚好消失,固定mainTableView的位置,显示listScrollView的滚动条
self.mainTableView.contentOffset = CGPointMake(self.mainTableView.contentOffset.x, topContentHeight);
}
}
#pragma mark - JXPagingListContainerViewDelegate
- (NSInteger)numberOfRowsInListContainerView:(JXPageListContainerView *)listContainerView {
NSArray *listViews = [self.delegate listViewsInPageListView:self];
return listViews.count;
}
- (UIView *)listContainerView:(JXPageListContainerView *)listContainerView listViewInRow:(NSInteger)row {
NSArray *listViews = [self.delegate listViewsInPageListView:self];
return listViews[row];
}
- (void)listContainerView:(JXPageListContainerView *)listContainerView willDisplayCellAtRow:(NSInteger)row {
NSArray *listViews = [self.delegate listViewsInPageListView:self];
self.currentScrollingListView = [listViews[row] listScrollView];
}
#pragma mark - JXCategoryViewDelegate
- (void)categoryView:(JXCategoryBaseView *)categoryView didSelectedItemAtIndex:(NSInteger)index {
if ([self.delegate respondsToSelector:@selector(pinCategoryView:didSelectedItemAtIndex:)]) {
[self.delegate pinCategoryView:categoryView didSelectedItemAtIndex:index];
}
NSArray *listViews = [self.delegate listViewsInPageListView:self];
[listViews[index] listViewLoadDataIfNeeded];
}
- (void)categoryView:(JXCategoryBaseView *)categoryView didClickSelectedItemAtIndex:(NSInteger)index {
//点击选中,会立马回调该方法。但是page还在左右切换。所以延迟0.25秒等左右切换结束再处理。
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self listViewDidSelectedAtIndex:index];
});
}
- (void)categoryView:(JXCategoryBaseView *)categoryView didScrollSelectedItemAtIndex:(NSInteger)index {
[self listViewDidSelectedAtIndex:index];
}
@end
//
// GMCustomNavigationAnimationProtocol.h
// Pods
//
// Created by wangyang on 2020/4/7.
//
#ifndef GMCustomNavigationAnimationProtocol_h
#define GMCustomNavigationAnimationProtocol_h
@protocol GMControllerAnimatedTransitioning <UIViewControllerAnimatedTransitioning>
@property(nonatomic, assign) UINavigationControllerOperation transitionType;
@end
@protocol GMCustomNavigationAnimationProtocol <NSObject>
@property (nonatomic, strong) id<GMControllerAnimatedTransitioning> navigationAnimation;
@end
#endif /* GMCustomNavigationAnimationProtocol_h */
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment