This commit is contained in:
Caleb
2025-11-21 12:00:40 +08:00
commit 18ea900c12
1189 changed files with 134605 additions and 0 deletions

BIN
Example/SubModules/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,16 @@
Pod::Spec.new do |s|
s.name = "FUBeautyComponent"
s.version = "1.0"
s.license = 'MIT'
s.summary = "Beauty component"
s.description = "The beauty component of FULiveDemo."
s.homepage = "https://github.com/Faceunity/FULiveDemo"
s.author = { 'faceunity' => 'dev@faceunity.com' }
s.platform = :ios, "12.0"
s.source = { "http": "https://github.com/Faceunity/FULiveDemo"}
s.source_files = '**/*.{h,m}'
s.resources = 'FUBeautyComponent/Resource/**/*.{json}'
s.requires_arc = true
s.dependency 'FURenderKit'
s.dependency 'FUCommonUIComponent'
end

View File

@@ -0,0 +1,493 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 55;
objects = {
/* Begin PBXBuildFile section */
E30500B42891364700C9408B /* FUBeautySkinViewModel.h in Headers */ = {isa = PBXBuildFile; fileRef = E30500B22891364700C9408B /* FUBeautySkinViewModel.h */; };
E30500B52891364700C9408B /* FUBeautySkinViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = E30500B32891364700C9408B /* FUBeautySkinViewModel.m */; };
E30500B82891365300C9408B /* FUBeautyShapeViewModel.h in Headers */ = {isa = PBXBuildFile; fileRef = E30500B62891365300C9408B /* FUBeautyShapeViewModel.h */; };
E30500B92891365300C9408B /* FUBeautyShapeViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = E30500B72891365300C9408B /* FUBeautyShapeViewModel.m */; };
E32C6DCE2877DEDC008A9C08 /* FUBeautySkinModel.h in Headers */ = {isa = PBXBuildFile; fileRef = E32C6DCC2877DEDC008A9C08 /* FUBeautySkinModel.h */; };
E32C6DCF2877DEDC008A9C08 /* FUBeautySkinModel.m in Sources */ = {isa = PBXBuildFile; fileRef = E32C6DCD2877DEDC008A9C08 /* FUBeautySkinModel.m */; };
E32C6DD22877DEEC008A9C08 /* FUBeautyShapeModel.h in Headers */ = {isa = PBXBuildFile; fileRef = E32C6DD02877DEEC008A9C08 /* FUBeautyShapeModel.h */; };
E32C6DD32877DEEC008A9C08 /* FUBeautyShapeModel.m in Sources */ = {isa = PBXBuildFile; fileRef = E32C6DD12877DEEC008A9C08 /* FUBeautyShapeModel.m */; };
E32C6DD62877E114008A9C08 /* FUBeautyShapeView.h in Headers */ = {isa = PBXBuildFile; fileRef = E32C6DD42877E114008A9C08 /* FUBeautyShapeView.h */; };
E32C6DD72877E114008A9C08 /* FUBeautyShapeView.m in Sources */ = {isa = PBXBuildFile; fileRef = E32C6DD52877E114008A9C08 /* FUBeautyShapeView.m */; };
E32D08E928758C5600884AE5 /* FUBeautyComponentManager.h in Headers */ = {isa = PBXBuildFile; fileRef = E32D08E828758C5600884AE5 /* FUBeautyComponentManager.h */; settings = {ATTRIBUTES = (Public, ); }; };
E32D09192876CEF300884AE5 /* FUBeautyFilterViewModel.h in Headers */ = {isa = PBXBuildFile; fileRef = E32D09172876CEF300884AE5 /* FUBeautyFilterViewModel.h */; };
E32D091A2876CEF300884AE5 /* FUBeautyFilterViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = E32D09182876CEF300884AE5 /* FUBeautyFilterViewModel.m */; };
E32D091D2876CF0F00884AE5 /* FUBeautyFilterModel.h in Headers */ = {isa = PBXBuildFile; fileRef = E32D091B2876CF0F00884AE5 /* FUBeautyFilterModel.h */; };
E32D091E2876CF0F00884AE5 /* FUBeautyFilterModel.m in Sources */ = {isa = PBXBuildFile; fileRef = E32D091C2876CF0F00884AE5 /* FUBeautyFilterModel.m */; };
E32D09222876CFDE00884AE5 /* beauty_filter.json in Resources */ = {isa = PBXBuildFile; fileRef = E32D09212876CFDE00884AE5 /* beauty_filter.json */; };
E32D09242876D01C00884AE5 /* beauty_skin.json in Resources */ = {isa = PBXBuildFile; fileRef = E32D09232876D01C00884AE5 /* beauty_skin.json */; };
E32D09262876D02E00884AE5 /* beauty_shape.json in Resources */ = {isa = PBXBuildFile; fileRef = E32D09252876D02E00884AE5 /* beauty_shape.json */; };
E33834A12861772B0023482D /* FUBeautySkinView.h in Headers */ = {isa = PBXBuildFile; fileRef = E338349F2861772B0023482D /* FUBeautySkinView.h */; };
E33834A22861772B0023482D /* FUBeautySkinView.m in Sources */ = {isa = PBXBuildFile; fileRef = E33834A02861772B0023482D /* FUBeautySkinView.m */; };
E33834BA2861BE8C0023482D /* FUBeautyFilterView.h in Headers */ = {isa = PBXBuildFile; fileRef = E33834B82861BE8C0023482D /* FUBeautyFilterView.h */; };
E33834BB2861BE8C0023482D /* FUBeautyFilterView.m in Sources */ = {isa = PBXBuildFile; fileRef = E33834B92861BE8C0023482D /* FUBeautyFilterView.m */; };
E34C47EE285338080040093C /* FUBeautyComponent.h in Headers */ = {isa = PBXBuildFile; fileRef = E34C47ED285338080040093C /* FUBeautyComponent.h */; settings = {ATTRIBUTES = (Public, ); }; };
E34C47FC2853381E0040093C /* FUBeautyDefine.h in Headers */ = {isa = PBXBuildFile; fileRef = E34C47F52853381E0040093C /* FUBeautyDefine.h */; };
E34C4831285347100040093C /* FUBeautyComponentManager.m in Sources */ = {isa = PBXBuildFile; fileRef = E34C482F285347100040093C /* FUBeautyComponentManager.m */; };
E3618FEC28583B9B00E7D8D2 /* FUBeautyDefine.m in Sources */ = {isa = PBXBuildFile; fileRef = E3618FEB28583B9B00E7D8D2 /* FUBeautyDefine.m */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
E30500B22891364700C9408B /* FUBeautySkinViewModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FUBeautySkinViewModel.h; sourceTree = "<group>"; };
E30500B32891364700C9408B /* FUBeautySkinViewModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FUBeautySkinViewModel.m; sourceTree = "<group>"; };
E30500B62891365300C9408B /* FUBeautyShapeViewModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FUBeautyShapeViewModel.h; sourceTree = "<group>"; };
E30500B72891365300C9408B /* FUBeautyShapeViewModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FUBeautyShapeViewModel.m; sourceTree = "<group>"; };
E32C6DCC2877DEDC008A9C08 /* FUBeautySkinModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FUBeautySkinModel.h; sourceTree = "<group>"; };
E32C6DCD2877DEDC008A9C08 /* FUBeautySkinModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FUBeautySkinModel.m; sourceTree = "<group>"; };
E32C6DD02877DEEC008A9C08 /* FUBeautyShapeModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FUBeautyShapeModel.h; sourceTree = "<group>"; };
E32C6DD12877DEEC008A9C08 /* FUBeautyShapeModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FUBeautyShapeModel.m; sourceTree = "<group>"; };
E32C6DD42877E114008A9C08 /* FUBeautyShapeView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FUBeautyShapeView.h; sourceTree = "<group>"; };
E32C6DD52877E114008A9C08 /* FUBeautyShapeView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FUBeautyShapeView.m; sourceTree = "<group>"; };
E32D08E828758C5600884AE5 /* FUBeautyComponentManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FUBeautyComponentManager.h; sourceTree = "<group>"; };
E32D09172876CEF300884AE5 /* FUBeautyFilterViewModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FUBeautyFilterViewModel.h; sourceTree = "<group>"; };
E32D09182876CEF300884AE5 /* FUBeautyFilterViewModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FUBeautyFilterViewModel.m; sourceTree = "<group>"; };
E32D091B2876CF0F00884AE5 /* FUBeautyFilterModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FUBeautyFilterModel.h; sourceTree = "<group>"; };
E32D091C2876CF0F00884AE5 /* FUBeautyFilterModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FUBeautyFilterModel.m; sourceTree = "<group>"; };
E32D09212876CFDE00884AE5 /* beauty_filter.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = beauty_filter.json; sourceTree = "<group>"; };
E32D09232876D01C00884AE5 /* beauty_skin.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = beauty_skin.json; sourceTree = "<group>"; };
E32D09252876D02E00884AE5 /* beauty_shape.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = beauty_shape.json; sourceTree = "<group>"; };
E338349B286173C10023482D /* FUCommonUIComponent.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FUCommonUIComponent.framework; sourceTree = BUILT_PRODUCTS_DIR; };
E338349F2861772B0023482D /* FUBeautySkinView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FUBeautySkinView.h; sourceTree = "<group>"; };
E33834A02861772B0023482D /* FUBeautySkinView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FUBeautySkinView.m; sourceTree = "<group>"; };
E33834B82861BE8C0023482D /* FUBeautyFilterView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FUBeautyFilterView.h; sourceTree = "<group>"; };
E33834B92861BE8C0023482D /* FUBeautyFilterView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FUBeautyFilterView.m; sourceTree = "<group>"; };
E34C47EA285338080040093C /* FUBeautyComponent.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FUBeautyComponent.framework; sourceTree = BUILT_PRODUCTS_DIR; };
E34C47ED285338080040093C /* FUBeautyComponent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FUBeautyComponent.h; sourceTree = "<group>"; };
E34C47F52853381E0040093C /* FUBeautyDefine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FUBeautyDefine.h; sourceTree = "<group>"; };
E34C482F285347100040093C /* FUBeautyComponentManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FUBeautyComponentManager.m; sourceTree = "<group>"; };
E3618FEB28583B9B00E7D8D2 /* FUBeautyDefine.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FUBeautyDefine.m; sourceTree = "<group>"; };
E369F8CF28D851550036AC5A /* FURenderKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FURenderKit.framework; path = ../FULiveDemo/FURenderKit/FURenderKit.framework; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
E34C47E7285338080040093C /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
E338349A286173C10023482D /* Frameworks */ = {
isa = PBXGroup;
children = (
E369F8CF28D851550036AC5A /* FURenderKit.framework */,
E338349B286173C10023482D /* FUCommonUIComponent.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
E34C47E0285338080040093C = {
isa = PBXGroup;
children = (
E34C47EC285338080040093C /* FUBeautyComponent */,
E34C47EB285338080040093C /* Products */,
E338349A286173C10023482D /* Frameworks */,
);
sourceTree = "<group>";
};
E34C47EB285338080040093C /* Products */ = {
isa = PBXGroup;
children = (
E34C47EA285338080040093C /* FUBeautyComponent.framework */,
);
name = Products;
sourceTree = "<group>";
};
E34C47EC285338080040093C /* FUBeautyComponent */ = {
isa = PBXGroup;
children = (
E34C47F42853381E0040093C /* Model */,
E34C47F92853381E0040093C /* View */,
E34C47F62853381E0040093C /* ViewModel */,
E3A787302859AB0000FBDAFF /* Resource */,
E34C47F52853381E0040093C /* FUBeautyDefine.h */,
E3618FEB28583B9B00E7D8D2 /* FUBeautyDefine.m */,
E32D08E828758C5600884AE5 /* FUBeautyComponentManager.h */,
E34C482F285347100040093C /* FUBeautyComponentManager.m */,
E34C47ED285338080040093C /* FUBeautyComponent.h */,
);
path = FUBeautyComponent;
sourceTree = "<group>";
};
E34C47F42853381E0040093C /* Model */ = {
isa = PBXGroup;
children = (
E32C6DCC2877DEDC008A9C08 /* FUBeautySkinModel.h */,
E32C6DCD2877DEDC008A9C08 /* FUBeautySkinModel.m */,
E32C6DD02877DEEC008A9C08 /* FUBeautyShapeModel.h */,
E32C6DD12877DEEC008A9C08 /* FUBeautyShapeModel.m */,
E32D091B2876CF0F00884AE5 /* FUBeautyFilterModel.h */,
E32D091C2876CF0F00884AE5 /* FUBeautyFilterModel.m */,
);
path = Model;
sourceTree = "<group>";
};
E34C47F62853381E0040093C /* ViewModel */ = {
isa = PBXGroup;
children = (
E30500B22891364700C9408B /* FUBeautySkinViewModel.h */,
E30500B32891364700C9408B /* FUBeautySkinViewModel.m */,
E30500B62891365300C9408B /* FUBeautyShapeViewModel.h */,
E30500B72891365300C9408B /* FUBeautyShapeViewModel.m */,
E32D09172876CEF300884AE5 /* FUBeautyFilterViewModel.h */,
E32D09182876CEF300884AE5 /* FUBeautyFilterViewModel.m */,
);
path = ViewModel;
sourceTree = "<group>";
};
E34C47F92853381E0040093C /* View */ = {
isa = PBXGroup;
children = (
E338349F2861772B0023482D /* FUBeautySkinView.h */,
E33834A02861772B0023482D /* FUBeautySkinView.m */,
E32C6DD42877E114008A9C08 /* FUBeautyShapeView.h */,
E32C6DD52877E114008A9C08 /* FUBeautyShapeView.m */,
E33834B82861BE8C0023482D /* FUBeautyFilterView.h */,
E33834B92861BE8C0023482D /* FUBeautyFilterView.m */,
);
path = View;
sourceTree = "<group>";
};
E3A787302859AB0000FBDAFF /* Resource */ = {
isa = PBXGroup;
children = (
E32D09232876D01C00884AE5 /* beauty_skin.json */,
E32D09252876D02E00884AE5 /* beauty_shape.json */,
E32D09212876CFDE00884AE5 /* beauty_filter.json */,
);
path = Resource;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
E34C47E5285338080040093C /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
E30500B82891365300C9408B /* FUBeautyShapeViewModel.h in Headers */,
E30500B42891364700C9408B /* FUBeautySkinViewModel.h in Headers */,
E32D08E928758C5600884AE5 /* FUBeautyComponentManager.h in Headers */,
E32C6DD22877DEEC008A9C08 /* FUBeautyShapeModel.h in Headers */,
E32C6DCE2877DEDC008A9C08 /* FUBeautySkinModel.h in Headers */,
E32C6DD62877E114008A9C08 /* FUBeautyShapeView.h in Headers */,
E33834BA2861BE8C0023482D /* FUBeautyFilterView.h in Headers */,
E32D09192876CEF300884AE5 /* FUBeautyFilterViewModel.h in Headers */,
E33834A12861772B0023482D /* FUBeautySkinView.h in Headers */,
E34C47FC2853381E0040093C /* FUBeautyDefine.h in Headers */,
E32D091D2876CF0F00884AE5 /* FUBeautyFilterModel.h in Headers */,
E34C47EE285338080040093C /* FUBeautyComponent.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
E34C47E9285338080040093C /* FUBeautyComponent */ = {
isa = PBXNativeTarget;
buildConfigurationList = E34C47F1285338080040093C /* Build configuration list for PBXNativeTarget "FUBeautyComponent" */;
buildPhases = (
E34C47E5285338080040093C /* Headers */,
E34C47E6285338080040093C /* Sources */,
E34C47E7285338080040093C /* Frameworks */,
E34C47E8285338080040093C /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = FUBeautyComponent;
productName = FUBeautyComponent;
productReference = E34C47EA285338080040093C /* FUBeautyComponent.framework */;
productType = "com.apple.product-type.framework";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
E34C47E1285338080040093C /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastUpgradeCheck = 1330;
TargetAttributes = {
E34C47E9285338080040093C = {
CreatedOnToolsVersion = 13.3.1;
};
};
};
buildConfigurationList = E34C47E4285338080040093C /* Build configuration list for PBXProject "FUBeautyComponent" */;
compatibilityVersion = "Xcode 13.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
"zh-Hans",
Base,
);
mainGroup = E34C47E0285338080040093C;
productRefGroup = E34C47EB285338080040093C /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
E34C47E9285338080040093C /* FUBeautyComponent */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
E34C47E8285338080040093C /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E32D09242876D01C00884AE5 /* beauty_skin.json in Resources */,
E32D09222876CFDE00884AE5 /* beauty_filter.json in Resources */,
E32D09262876D02E00884AE5 /* beauty_shape.json in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
E34C47E6285338080040093C /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E34C4831285347100040093C /* FUBeautyComponentManager.m in Sources */,
E32C6DD32877DEEC008A9C08 /* FUBeautyShapeModel.m in Sources */,
E33834A22861772B0023482D /* FUBeautySkinView.m in Sources */,
E32D091A2876CEF300884AE5 /* FUBeautyFilterViewModel.m in Sources */,
E32D091E2876CF0F00884AE5 /* FUBeautyFilterModel.m in Sources */,
E30500B92891365300C9408B /* FUBeautyShapeViewModel.m in Sources */,
E30500B52891364700C9408B /* FUBeautySkinViewModel.m in Sources */,
E33834BB2861BE8C0023482D /* FUBeautyFilterView.m in Sources */,
E32C6DCF2877DEDC008A9C08 /* FUBeautySkinModel.m in Sources */,
E32C6DD72877E114008A9C08 /* FUBeautyShapeView.m in Sources */,
E3618FEC28583B9B00E7D8D2 /* FUBeautyDefine.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
E34C47EF285338080040093C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.4;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
E34C47F0285338080040093C /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.4;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
E34C47F2285338080040093C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 35A6KD62ZJ;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)/../FULiveDemo/FURenderKit",
);
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = faceunity.FUBeautyComponent;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
E34C47F3285338080040093C /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 35A6KD62ZJ;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)/../FULiveDemo/FURenderKit",
);
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = faceunity.FUBeautyComponent;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
E34C47E4285338080040093C /* Build configuration list for PBXProject "FUBeautyComponent" */ = {
isa = XCConfigurationList;
buildConfigurations = (
E34C47EF285338080040093C /* Debug */,
E34C47F0285338080040093C /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
E34C47F1285338080040093C /* Build configuration list for PBXNativeTarget "FUBeautyComponent" */ = {
isa = XCConfigurationList;
buildConfigurations = (
E34C47F2285338080040093C /* Debug */,
E34C47F3285338080040093C /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = E34C47E1285338080040093C /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

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

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1330"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "E34C47E9285338080040093C"
BuildableName = "FUBeautyComponent.framework"
BlueprintName = "FUBeautyComponent"
ReferencedContainer = "container:FUBeautyComponent.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "E34C47E9285338080040093C"
BuildableName = "FUBeautyComponent.framework"
BlueprintName = "FUBeautyComponent"
ReferencedContainer = "container:FUBeautyComponent.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

Binary file not shown.

View File

@@ -0,0 +1,17 @@
//
// FUBeautyComponent.h
// FUBeautyComponent
//
// Created by 项林平 on 2022/6/10.
//
#import <Foundation/Foundation.h>
#import "FUBeautyComponentManager.h"
//! Project version number for FUBeautyComponent.
FOUNDATION_EXPORT double FUBeautyComponentVersionNumber;
//! Project version string for FUBeautyComponent.
FOUNDATION_EXPORT const unsigned char FUBeautyComponentVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <FUBeautyComponent/PublicHeader.h>

View File

@@ -0,0 +1,52 @@
//
// FUBeautyComponentManager.h
// FUBeautyComponentManager
//
// Created by 项林平 on 2022/6/10.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@protocol FUBeautyComponentDelegate <NSObject>
@optional
- (void)beautyComponentViewHeightDidChange:(CGFloat)height;
- (void)beautyComponentDidTouchDownComparison;
- (void)beautyComponentDidTouchUpComparison;
/// 提示
- (void)beautyComponentNeedsDisplayPromptContent:(NSString *)content;
@end
@interface FUBeautyComponentManager : NSObject
@property (nonatomic, weak) id<FUBeautyComponentDelegate> delegate;
/// 美颜视图总高度
@property (nonatomic, assign, readonly) CGFloat componentViewHeight;
/// 美颜视图选中功能索引,默认-1-1表示未选中
@property (nonatomic, assign, readonly) NSInteger selectedIndex;
@property (nonatomic, weak, readonly) UIView *targetView;
+ (instancetype)sharedManager;
/// 销毁
+ (void)destory;
/// 在目标视图中添加美颜组件视图(固定位置为目标视图底部)
/// @param view 目标视图
- (void)addComponentViewToView:(UIView *)view;
/// 在父视图中移除美颜组件视图
- (void)removeComponentView;
//设置所有效果为上次数据
- (void)beautyStyleSetAllDefaultValues;
@property (nonatomic, assign)BOOL enableRender;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,329 @@
//
// FUBeautyComponentManager.m
// FUBeautyComponentManager
//
// Created by on 2022/6/10.
//
#import "FUBeautyComponentManager.h"
#import "FUBeautyDefine.h"
#import "FUBeautySkinView.h"
#import "FUBeautyShapeView.h"
#import "FUBeautyFilterView.h"
#import <FUCommonUIComponent/FUCommonUIComponent.h>
#import <FURenderKit/FURenderKit.h>
static FUBeautyComponentManager *beautyComponentManager = nil;
static dispatch_once_t onceToken;
@interface FUBeautyComponentManager ()<FUSegmentBarDelegate, FUBeautyFilterViewDelegate>
@property (nonatomic, weak) UIView *targetView;
@property (nonatomic, strong) FUSegmentBar *categoryView;
@property (nonatomic, strong) UIButton *compareButton;
@property (nonatomic, strong) FUBeautySkinView *beautySkinView;
@property (nonatomic, strong) FUBeautyShapeView *beautyShapeView;
@property (nonatomic, strong) FUBeautyFilterView *beautyFilterView;
@property (nonatomic, strong) FUBeautySkinViewModel *beautySkinViewModel;
@property (nonatomic, strong) FUBeautyShapeViewModel *beautyShapeViewModel;
@property (nonatomic, strong) FUBeautyFilterViewModel *beautyFilterViewModel;
@property (nonatomic, assign) NSInteger selectedIndex;
@property (nonatomic, strong) UIControl *control;
@end
@implementation FUBeautyComponentManager
#pragma mark - Class methods
+ (instancetype)sharedManager {
dispatch_once(&onceToken, ^{
beautyComponentManager = [[FUBeautyComponentManager alloc] init];
beautyComponentManager.enableRender = true;
});
return beautyComponentManager;
}
+ (void)destory {
onceToken = 0;
beautyComponentManager = nil;
}
#pragma mark - Initializer
- (instancetype)init {
self = [super init];
if (self) {
self.selectedIndex = FUBeautyCategoryNone;
}
return self;
}
#pragma mark - Instance methods
- (void)addComponentViewToView:(UIView *)view {
NSAssert(view != nil, @"FUBeautyComponent: view can not be nil!");
self.targetView = view;
// [self removeComponentView];
self.control.frame = view.bounds;
[self.targetView addSubview:self.control];
// [self.targetView addSubview:self.compareButton];
[self.targetView addSubview:self.beautySkinView];
[self.targetView addSubview:self.beautyShapeView];
[self.targetView addSubview:self.beautyFilterView];
[self.targetView addSubview:self.categoryView];
if (self.categoryView.selectedIndex < 0) {
[self.categoryView selectItemAtIndex:0];
}
}
- (void)removeComponentView {
[self.control removeFromSuperview];
// if (self.compareButton.superview) {
// [self.compareButton removeFromSuperview];
// }
if (self.beautySkinView.superview) {
[self.beautySkinView removeFromSuperview];
}
if (self.beautyShapeView.superview) {
[self.beautyShapeView removeFromSuperview];
}
if (self.beautyFilterView.superview) {
[self.beautyFilterView removeFromSuperview];
}
if (self.categoryView.superview) {
[self.categoryView removeFromSuperview];
}
_targetView = nil;
}
#pragma mark - Private methods
- (void)controlClick {
[self removeComponentView];
}
- (void)showEffectView:(UIView *)view animated:(BOOL)animated completion:(void (^)(void))completion {
view.hidden = NO;
self.compareButton.hidden = NO;
if (animated) {
[UIView animateWithDuration:0.2 animations:^{
view.transform = CGAffineTransformMakeTranslation(0, -CGRectGetHeight(view.frame));
self.compareButton.transform = CGAffineTransformMakeTranslation(0, -CGRectGetHeight(view.frame));
} completion:^(BOOL finished) {
!completion ?: completion();
}];
} else {
view.transform = CGAffineTransformMakeTranslation(0, -CGRectGetHeight(view.frame));
self.compareButton.transform = CGAffineTransformMakeTranslation(0, -CGRectGetHeight(view.frame));
!completion ?: completion();
}
if (self.delegate && [self.delegate respondsToSelector:@selector(beautyComponentViewHeightDidChange:)]) {
[self.delegate beautyComponentViewHeightDidChange:CGRectGetHeight(view.frame) + CGRectGetHeight(self.categoryView.frame)];
}
}
- (void)hideEffectView:(UIView *)view animated:(BOOL)animated completion:(void (^)(void))completion {
if (animated) {
[UIView animateWithDuration:0.2 animations:^{
view.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) {
view.hidden = YES;
!completion ?: completion();
}];
} else {
view.transform = CGAffineTransformIdentity;
view.hidden = YES;
!completion ?: completion();
}
}
- (UIView *)showingView {
UIView *view;
switch (self.selectedIndex) {
case FUBeautyCategorySkin:{
view = self.beautySkinView;
}
break;
case FUBeautyCategoryShape:{
view = self.beautyShapeView;
}
break;
case FUBeautyCategoryFilter:{
view = self.beautyFilterView;
}
break;
default:
break;
}
return view;
}
#pragma mark - Event response
- (void)compareTouchDownAction {
if (self.delegate && [self.delegate respondsToSelector:@selector(beautyComponentDidTouchDownComparison)]) {
[self.delegate beautyComponentDidTouchDownComparison];
}
}
- (void)compareTouchUpAction {
if (self.delegate && [self.delegate respondsToSelector:@selector(beautyComponentDidTouchUpComparison)]) {
[self.delegate beautyComponentDidTouchUpComparison];
}
}
#pragma mark - FUSegmentBarDelegate
- (void)segmentBar:(FUSegmentBar *)segmentBar didSelectItemAtIndex:(NSUInteger)index {
if(index == 3) {
self.enableRender = !self.enableRender;
[self hideEffectView:[self showingView] animated:YES completion:^{
segmentBar.userInteractionEnabled = YES;
}];
if (self.enableRender) {
[self.categoryView selectItemAtIndex:0];
}
else {
self.selectedIndex = index;
}
if (self.delegate && [self.delegate respondsToSelector:@selector(beautyComponentViewHeightDidChange:)]) {
[self.delegate beautyComponentViewHeightDidChange:CGRectGetHeight(self.categoryView.frame)];
}
return;
}
if (index == self.selectedIndex) {
//
segmentBar.userInteractionEnabled = NO;
[self hideEffectView:[self showingView] animated:YES completion:^{
segmentBar.userInteractionEnabled = YES;
}];
self.compareButton.transform = CGAffineTransformIdentity;
self.compareButton.hidden = YES;
[segmentBar selectItemAtIndex:-1];
self.selectedIndex = FUBeautyCategoryNone;
if (self.delegate && [self.delegate respondsToSelector:@selector(beautyComponentViewHeightDidChange:)]) {
[self.delegate beautyComponentViewHeightDidChange:CGRectGetHeight(self.categoryView.frame)];
}
} else {
segmentBar.userInteractionEnabled = NO;
if (self.selectedIndex > FUBeautyCategoryNone) {
[self hideEffectView:[self showingView] animated:NO completion:nil];
}
self.selectedIndex = index;
[self showEffectView:[self showingView] animated:YES completion:^{
segmentBar.userInteractionEnabled = YES;
}];
}
}
#pragma mark - FUBeautyFilterViewDelegate
- (void)beautyFilterViewDidChangeFilter:(NSString *)name {
if (self.delegate && [self.delegate respondsToSelector:@selector(beautyComponentNeedsDisplayPromptContent:)]) {
[self.delegate beautyComponentNeedsDisplayPromptContent:name];
}
}
- (void)beautyStyleSetAllDefaultValues {
//
[self.beautySkinViewModel setAllSkinValues];
[self.beautyShapeViewModel setAllShapeValues];
[self.beautyFilterViewModel setCurrentFilter];
}
#pragma mark - Getters
- (FUSegmentBar *)categoryView {
if (!_categoryView) {
NSArray<NSString *> *categories = @[FUBeautyStringWithKey(@"skin"), FUBeautyStringWithKey(@"shape"), FUBeautyStringWithKey(@"filter")];
FUSegmentBarConfigurations *configurations = [[FUSegmentBarConfigurations alloc] init];
configurations.titleFont = [UIFont systemFontOfSize:15];
_categoryView = [[FUSegmentBar alloc] initWithFrame:CGRectMake(0, CGRectGetHeight(self.targetView.bounds) - FUBeautyHeightIncludeBottomSafeArea(FUBeautyCategoryViewHeight), CGRectGetWidth(self.targetView.bounds), FUBeautyHeightIncludeBottomSafeArea(FUBeautyCategoryViewHeight)) titles:categories configuration:configurations];
_categoryView.delegate = self;
}
return _categoryView;
}
//- (UIButton *)compareButton {
// if (!_compareButton) {
// _compareButton = [UIButton buttonWithType:UIButtonTypeCustom];
// _compareButton.frame = CGRectMake(15, CGRectGetMinY(self.categoryView.frame) - 54, 44, 44);
// [_compareButton setImage:[UIImage imageNamed:@"compare_item"] forState:UIControlStateNormal];
// [_compareButton addTarget:self action:@selector(compareTouchDownAction) forControlEvents:UIControlEventTouchDown];
// [_compareButton addTarget:self action:@selector(compareTouchUpAction) forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchUpOutside];
// _compareButton.hidden = YES;
// }
// return _compareButton;
//}
- (FUBeautySkinView *)beautySkinView {
if (!_beautySkinView) {
_beautySkinView = [[FUBeautySkinView alloc] initWithFrame:CGRectMake(0, CGRectGetMinY(self.categoryView.frame), CGRectGetWidth(self.targetView.bounds), FUBeautyFunctionViewOverallHeight) viewModel:self.beautySkinViewModel];
_beautySkinView.hidden = YES;
}
return _beautySkinView;
}
- (FUBeautyShapeView *)beautyShapeView {
if (!_beautyShapeView) {
_beautyShapeView = [[FUBeautyShapeView alloc] initWithFrame:CGRectMake(0, CGRectGetMinY(self.categoryView.frame), CGRectGetWidth(self.targetView.bounds), FUBeautyFunctionViewOverallHeight) viewModel:self.beautyShapeViewModel];
_beautyShapeView.hidden = YES;
}
return _beautyShapeView;
}
- (FUBeautyFilterView *)beautyFilterView {
if (!_beautyFilterView) {
_beautyFilterView = [[FUBeautyFilterView alloc] initWithFrame:CGRectMake(0, CGRectGetMinY(self.categoryView.frame), CGRectGetWidth(self.targetView.bounds), FUBeautyFunctionViewOverallHeight) viewModel:self.beautyFilterViewModel];
_beautyFilterView.hidden = YES;
_beautyFilterView.delegate = self;
}
return _beautyFilterView;
}
- (FUBeautySkinViewModel *)beautySkinViewModel {
if (!_beautySkinViewModel) {
_beautySkinViewModel = [[FUBeautySkinViewModel alloc] init];
}
return _beautySkinViewModel;
}
- (FUBeautyShapeViewModel *)beautyShapeViewModel {
if (!_beautyShapeViewModel) {
_beautyShapeViewModel = [[FUBeautyShapeViewModel alloc] init];
}
return _beautyShapeViewModel;
}
- (FUBeautyFilterViewModel *)beautyFilterViewModel {
if (!_beautyFilterViewModel) {
_beautyFilterViewModel = [[FUBeautyFilterViewModel alloc] init];
}
return _beautyFilterViewModel;
}
- (CGFloat)componentViewHeight {
if (self.selectedIndex == FUBeautyCategoryNone) {
return FUBeautyHeightIncludeBottomSafeArea(FUBeautyCategoryViewHeight);
} else {
return FUBeautyHeightIncludeBottomSafeArea(FUBeautyCategoryViewHeight) + FUBeautyFunctionViewOverallHeight;
}
}
- (UIControl *)control {
if (!_control) {
_control = [[UIControl alloc] init];
_control.backgroundColor = [UIColor colorWithWhite:0 alpha:0.1];
[_control addTarget:self action:@selector(controlClick) forControlEvents:UIControlEventTouchUpInside];
}
return _control;
}
@end

View File

@@ -0,0 +1,98 @@
//
// FUBeautyDefine.h
// FUBeautyUIComponent
//
// Created by 项林平 on 2022/6/9.
//
#import <AVFoundation/AVFoundation.h>
#import <UIKit/UIKit.h>
#ifndef FUBeautyDefine_h
#define FUBeautyDefine_h
#define FUBeautyStringWithKey(aKey) NSLocalizedString(aKey, nil)
#pragma mark - Const
extern const CGFloat FUBeautyCategoryViewHeight;
extern const CGFloat FUBeautyFunctionViewOverallHeight;
extern const CGFloat FUBeautyFunctionSliderHeight;
extern NSString * const FUPersistentBeautySkinKey;
extern NSString * const FUPersistentBeautyShapeKey;
extern NSString * const FUPersistentBeautyFilterKey;
extern NSString * const FUPersistentBeautySelectedFilterIndexKey;
#pragma mark - Enum
typedef NS_ENUM(NSInteger, FUBeautyCategory) {
FUBeautyCategoryNone = -1,
FUBeautyCategorySkin = 0, //美肤
FUBeautyCategoryShape = 1, //美型
FUBeautyCategoryFilter = 2 //滤镜
};
typedef NS_ENUM(NSUInteger, FUBeautySkin) {
FUBeautySkinBlurLevel = 0,
FUBeautySkinColorLevel,
FUBeautySkinRedLevel,
FUBeautySkinSharpen,
FUBeautySkinFaceThreed,
FUBeautySkinEyeBright,
FUBeautySkinToothWhiten,
FUBeautySkinRemovePouchStrength,
FUBeautySkinRemoveNasolabialFoldsStrength
};
typedef NS_ENUM(NSUInteger, FUBeautyShape) {
FUBeautyShapeCheekThinning = 0,
FUBeautyShapeCheekV,
FUBeautyShapeCheekNarrow,
FUBeautyShapeCheekShort,
FUBeautyShapeCheekSmall,
FUBeautyShapeCheekbones,
FUBeautyShapeLowerJaw,
FUBeautyShapeEyeEnlarging,
FUBeautyShapeEyeCircle,
FUBeautyShapeChin,
FUBeautyShapeForehead,
FUBeautyShapeNose,
FUBeautyShapeMouth,
FUBeautyShapeLipThick,
FUBeautyShapeEyeHeight,
FUBeautyShapeCanthus,
FUBeautyShapeEyeLid,
FUBeautyShapeEyeSpace,
FUBeautyShapeEyeRotate,
FUBeautyShapeLongNose,
FUBeautyShapePhiltrum,
FUBeautyShapeSmile,
FUBeautyShapeBrowHeight,
FUBeautyShapeBrowSpace,
FUBeautyShapeBrowThick
};
#pragma mark - Inline methods
static inline CGFloat FUBeautyHeightIncludeBottomSafeArea(CGFloat height) {
if (@available(iOS 11.0, *)) {
height += [UIApplication sharedApplication].delegate.window.safeAreaInsets.bottom;
}
return height;
}
static inline UIImage * FUBeautyImageNamed(NSString *name) {
NSString *path = [[NSBundle mainBundle] pathForResource:@"FUBeautyComponent" ofType:@"framework" inDirectory:@"Frameworks"];
NSBundle *bundle = [NSBundle bundleWithPath:path];
return [UIImage imageNamed:name inBundle:bundle compatibleWithTraitCollection:nil];;
}
#endif /* FUBeautyDefine_h */

View File

@@ -0,0 +1,23 @@
//
// FUBeautyDefine.m
// FUBeautyComponent
//
// Created by on 2022/6/14.
//
#import "FUBeautyDefine.h"
const CGFloat FUBeautyCategoryViewHeight = 49.f;
const CGFloat FUBeautyFunctionViewOverallHeight = 141.f;
const CGFloat FUBeautyFunctionSliderHeight = 30.f;
NSString * const FUPersistentBeautySkinKey = @"FUPersistentBeautySkin";
NSString * const FUPersistentBeautyShapeKey = @"FUPersistentBeautyShape";
NSString * const FUPersistentBeautyFilterKey = @"FUPersistentBeautyFilter";
NSString * const FUPersistentBeautySelectedFilterIndexKey = @"FUPersistentBeautySelectedFilterIndex";

View File

@@ -0,0 +1,20 @@
//
// FUBeautyFilterModel.h
// FUBeautyComponent
//
// Created by 项林平 on 2022/7/7.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface FUBeautyFilterModel : NSObject
@property (nonatomic, assign) NSInteger filterIndex;
@property (nonatomic, copy) NSString *filterName;
@property (nonatomic, assign) double filterLevel;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,12 @@
//
// FUBeautyFilterModel.m
// FUBeautyComponent
//
// Created by on 2022/7/7.
//
#import "FUBeautyFilterModel.h"
@implementation FUBeautyFilterModel
@end

View File

@@ -0,0 +1,29 @@
//
// FUBeautyShapeModel.h
// FUBeautyComponent
//
// Created by 项林平 on 2022/7/8.
//
#import <Foundation/Foundation.h>
#import "FUBeautyDefine.h"
NS_ASSUME_NONNULL_BEGIN
@interface FUBeautyShapeModel : NSObject
@property (nonatomic, copy) NSString *name;
/// 美型类型
@property (nonatomic, assign) FUBeautyShape type;
/// 当前值
@property (nonatomic) double currentValue;
/// 默认值
@property (nonatomic) double defaultValue;
/// 默认值是否中位数
@property (nonatomic, assign) BOOL defaultValueInMiddle;
/// 是否区分设备性能
@property (nonatomic, assign) BOOL differentiateDevicePerformance;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,12 @@
//
// FUBeautyShapeModel.m
// FUBeautyComponent
//
// Created by on 2022/7/8.
//
#import "FUBeautyShapeModel.h"
@implementation FUBeautyShapeModel
@end

View File

@@ -0,0 +1,29 @@
//
// FUBeautySkinModel.h
// FUBeautyComponent
//
// Created by 项林平 on 2022/7/8.
//
#import <Foundation/Foundation.h>
#import "FUBeautyDefine.h"
NS_ASSUME_NONNULL_BEGIN
@interface FUBeautySkinModel : NSObject
@property (nonatomic, copy) NSString *name;
/// 美肤类型
@property (nonatomic, assign) FUBeautySkin type;
/// 当前值
@property (nonatomic, assign) double currentValue;
/// 默认值
@property (nonatomic, assign) double defaultValue;
/// 默认值是否中位数
@property (nonatomic, assign) BOOL defaultValueInMiddle;
/// 实际值对应0.0-1.0的倍率
@property (nonatomic, assign) NSInteger ratio;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,12 @@
//
// FUBeautySkinModel.m
// FUBeautyComponent
//
// Created by on 2022/7/8.
//
#import "FUBeautySkinModel.h"
@implementation FUBeautySkinModel
@end

View File

@@ -0,0 +1,322 @@
[
{
"filterIndex":0,
"filterName":"origin",
"filterLevel":0.4
},
{
"filterIndex":1,
"filterName":"ziran1",
"filterLevel":0.4
},
{
"filterIndex":2,
"filterName":"ziran2",
"filterLevel":0.4
},
{
"filterIndex":3,
"filterName":"ziran3",
"filterLevel":0.4
},
{
"filterIndex":4,
"filterName":"ziran4",
"filterLevel":0.4
},
{
"filterIndex":5,
"filterName":"ziran5",
"filterLevel":0.4
},
{
"filterIndex":6,
"filterName":"ziran6",
"filterLevel":0.4
},
{
"filterIndex":7,
"filterName":"ziran7",
"filterLevel":0.4
},
{
"filterIndex":8,
"filterName":"ziran8",
"filterLevel":0.4
},
{
"filterIndex":9,
"filterName":"zhiganhui1",
"filterLevel":0.4
},
{
"filterIndex":10,
"filterName":"zhiganhui2",
"filterLevel":0.4
},
{
"filterIndex":11,
"filterName":"zhiganhui3",
"filterLevel":0.4
},
{
"filterIndex":12,
"filterName":"zhiganhui4",
"filterLevel":0.4
},
{
"filterIndex":13,
"filterName":"zhiganhui5",
"filterLevel":0.4
},
{
"filterIndex":14,
"filterName":"zhiganhui6",
"filterLevel":0.4
},
{
"filterIndex":15,
"filterName":"zhiganhui7",
"filterLevel":0.4
},
{
"filterIndex":16,
"filterName":"zhiganhui8",
"filterLevel":0.4
},
{
"filterIndex":17,
"filterName":"mitao1",
"filterLevel":0.4
},
{
"filterIndex":18,
"filterName":"mitao2",
"filterLevel":0.4
},
{
"filterIndex":19,
"filterName":"mitao3",
"filterLevel":0.4
},
{
"filterIndex":20,
"filterName":"mitao4",
"filterLevel":0.4
},
{
"filterIndex":21,
"filterName":"mitao5",
"filterLevel":0.4
},
{
"filterIndex":22,
"filterName":"mitao6",
"filterLevel":0.4
},
{
"filterIndex":23,
"filterName":"mitao7",
"filterLevel":0.4
},
{
"filterIndex":24,
"filterName":"mitao8",
"filterLevel":0.4
},
{
"filterIndex":25,
"filterName":"bailiang1",
"filterLevel":0.4
},
{
"filterIndex":26,
"filterName":"bailiang2",
"filterLevel":0.4
},
{
"filterIndex":27,
"filterName":"bailiang3",
"filterLevel":0.4
},
{
"filterIndex":28,
"filterName":"bailiang4",
"filterLevel":0.4
},
{
"filterIndex":29,
"filterName":"bailiang5",
"filterLevel":0.4
},
{
"filterIndex":30,
"filterName":"bailiang6",
"filterLevel":0.4
},
{
"filterIndex":31,
"filterName":"bailiang7",
"filterLevel":0.4
},
{
"filterIndex":32,
"filterName":"fennen1",
"filterLevel":0.4
},
{
"filterIndex":33,
"filterName":"fennen2",
"filterLevel":0.4
},
{
"filterIndex":34,
"filterName":"fennen3",
"filterLevel":0.4
},
{
"filterIndex":35,
"filterName":"fennen5",
"filterLevel":0.4
},
{
"filterIndex":36,
"filterName":"fennen6",
"filterLevel":0.4
},
{
"filterIndex":37,
"filterName":"fennen7",
"filterLevel":0.4
},
{
"filterIndex":38,
"filterName":"fennen8",
"filterLevel":0.4
},
{
"filterIndex":39,
"filterName":"lengsediao1",
"filterLevel":0.4
},
{
"filterIndex":40,
"filterName":"lengsediao2",
"filterLevel":0.4
},
{
"filterIndex":41,
"filterName":"lengsediao3",
"filterLevel":0.4
},
{
"filterIndex":42,
"filterName":"lengsediao4",
"filterLevel":0.4
},
{
"filterIndex":43,
"filterName":"lengsediao7",
"filterLevel":0.4
},
{
"filterIndex":44,
"filterName":"lengsediao8",
"filterLevel":0.4
},
{
"filterIndex":45,
"filterName":"lengsediao11",
"filterLevel":0.4
},
{
"filterIndex":46,
"filterName":"nuansediao1",
"filterLevel":0.4
},
{
"filterIndex":47,
"filterName":"nuansediao2",
"filterLevel":0.4
},
{
"filterIndex":48,
"filterName":"gexing1",
"filterLevel":0.4
},
{
"filterIndex":49,
"filterName":"gexing2",
"filterLevel":0.4
},
{
"filterIndex":50,
"filterName":"gexing3",
"filterLevel":0.4
},
{
"filterIndex":51,
"filterName":"gexing4",
"filterLevel":0.4
},
{
"filterIndex":52,
"filterName":"gexing5",
"filterLevel":0.4
},
{
"filterIndex":53,
"filterName":"gexing7",
"filterLevel":0.4
},
{
"filterIndex":54,
"filterName":"gexing10",
"filterLevel":0.4
},
{
"filterIndex":55,
"filterName":"gexing11",
"filterLevel":0.4
},
{
"filterIndex":56,
"filterName":"xiaoqingxin1",
"filterLevel":0.4
},
{
"filterIndex":57,
"filterName":"xiaoqingxin3",
"filterLevel":0.4
},
{
"filterIndex":58,
"filterName":"xiaoqingxin4",
"filterLevel":0.4
},
{
"filterIndex":59,
"filterName":"xiaoqingxin6",
"filterLevel":0.4
},
{
"filterIndex":60,
"filterName":"heibai1",
"filterLevel":0.4
},
{
"filterIndex":61,
"filterName":"heibai2",
"filterLevel":0.4
},
{
"filterIndex":62,
"filterName":"heibai3",
"filterLevel":0.4
},
{
"filterIndex":63,
"filterName":"heibai4",
"filterLevel":0.4
}
]

View File

@@ -0,0 +1,202 @@
[
{
"name":"瘦脸",
"type":0,
"currentValue":0.0,
"defaultValue":0.0,
"defaultValueInMiddle":false,
"differentiateDevicePerformance":false
},
{
"name":"v脸",
"type":1,
"currentValue":0.5,
"defaultValue":0.5,
"defaultValueInMiddle":false,
"differentiateDevicePerformance":false
},
{
"name":"窄脸",
"type":2,
"currentValue":0.0,
"defaultValue":0.0,
"defaultValueInMiddle":false,
"differentiateDevicePerformance":false
},
{
"name":"短脸",
"type":3,
"currentValue":0.0,
"defaultValue":0.0,
"defaultValueInMiddle":false,
"differentiateDevicePerformance":false
},
{
"name":"小脸",
"type":4,
"currentValue":0.0,
"defaultValue":0.0,
"defaultValueInMiddle":false,
"differentiateDevicePerformance":false
},
{
"name":"瘦颧骨",
"type":5,
"currentValue":0.0,
"defaultValue":0.0,
"defaultValueInMiddle":false,
"differentiateDevicePerformance":false
},
{
"name":"瘦下颌骨",
"type":6,
"currentValue":0.0,
"defaultValue":0.0,
"defaultValueInMiddle":false,
"differentiateDevicePerformance":false
},
{
"name":"大眼",
"type":7,
"currentValue":0.4,
"defaultValue":0.4,
"defaultValueInMiddle":false,
"differentiateDevicePerformance":false
},
{
"name":"圆眼",
"type":8,
"currentValue":0.0,
"defaultValue":0.0,
"defaultValueInMiddle":false,
"differentiateDevicePerformance":false
},
{
"name":"下巴",
"type":9,
"currentValue":0.3,
"defaultValue":0.3,
"defaultValueInMiddle":true,
"differentiateDevicePerformance":false
},
{
"name":"额头",
"type":10,
"currentValue":0.3,
"defaultValue":0.3,
"defaultValueInMiddle":true,
"differentiateDevicePerformance":false
},
{
"name":"瘦鼻",
"type":11,
"currentValue":0.5,
"defaultValue":0.5,
"defaultValueInMiddle":false,
"differentiateDevicePerformance":false
},
{
"name":"嘴型",
"type":12,
"currentValue":0.4,
"defaultValue":0.4,
"defaultValueInMiddle":true,
"differentiateDevicePerformance":false
},
{
"name":"嘴唇厚度",
"type":13,
"currentValue":0.5,
"defaultValue":0.5,
"defaultValueInMiddle":true,
"differentiateDevicePerformance":true
},
{
"name":"眼睛位置",
"type":14,
"currentValue":0.5,
"defaultValue":0.5,
"defaultValueInMiddle":true,
"differentiateDevicePerformance":true
},
{
"name":"开眼角",
"type":15,
"currentValue":0.0,
"defaultValue":0.0,
"defaultValueInMiddle":false,
"differentiateDevicePerformance":false
},
{
"name":"眼睑下至",
"type":16,
"currentValue":0.0,
"defaultValue":0.0,
"defaultValueInMiddle":false,
"differentiateDevicePerformance":true
},
{
"name":"眼距",
"type":17,
"currentValue":0.5,
"defaultValue":0.5,
"defaultValueInMiddle":true,
"differentiateDevicePerformance":false
},
{
"name":"眼睛角度",
"type":18,
"currentValue":0.5,
"defaultValue":0.5,
"defaultValueInMiddle":true,
"differentiateDevicePerformance":false
},
{
"name":"长鼻",
"type":19,
"currentValue":0.5,
"defaultValue":0.5,
"defaultValueInMiddle":true,
"differentiateDevicePerformance":false
},
{
"name":"缩人中",
"type":20,
"currentValue":0.5,
"defaultValue":0.5,
"defaultValueInMiddle":true,
"differentiateDevicePerformance":false
},
{
"name":"微笑嘴角",
"type":21,
"currentValue":0.0,
"defaultValue":0.0,
"defaultValueInMiddle":false,
"differentiateDevicePerformance":false
},
{
"name":"眉毛上下",
"type":22,
"currentValue":0.5,
"defaultValue":0.5,
"defaultValueInMiddle":true,
"differentiateDevicePerformance":true
},
{
"name":"眉间距",
"type":23,
"currentValue":0.5,
"defaultValue":0.5,
"defaultValueInMiddle":true,
"differentiateDevicePerformance":true
},
{
"name":"眉毛粗细",
"type":24,
"currentValue":0.5,
"defaultValue":0.5,
"defaultValueInMiddle":true,
"differentiateDevicePerformance":true
}
]

View File

@@ -0,0 +1,74 @@
[
{
"name":"磨皮",
"type":0,
"currentValue":2,
"defaultValue":2,
"defaultValueInMiddle":false,
"ratio":6
},
{
"name":"美白",
"type":1,
"currentValue":0.3,
"defaultValue":0.3,
"defaultValueInMiddle":false,
"ratio":1
},
{
"name":"红润",
"type":2,
"currentValue":0.3,
"defaultValue":0.3,
"defaultValueInMiddle":false,
"ratio":1
},
{
"name":"锐化",
"type":3,
"currentValue":0.2,
"defaultValue":0.2,
"defaultValueInMiddle":false,
"ratio":1
},
{
"name":"五官立体",
"type":4,
"currentValue":0.0,
"defaultValue":0.0,
"defaultValueInMiddle":false,
"ratio":1
},
{
"name":"亮眼",
"type":5,
"currentValue":0.0,
"defaultValue":0.0,
"defaultValueInMiddle":false,
"ratio":1
},
{
"name":"美牙",
"type":6,
"currentValue":0.0,
"defaultValue":0.0,
"defaultValueInMiddle":false,
"ratio":1
},
{
"name":"去黑眼圈",
"type":7,
"currentValue":0.0,
"defaultValue":0.0,
"defaultValueInMiddle":false,
"ratio":1
},
{
"name":"去法令纹",
"type":8,
"currentValue":0.0,
"defaultValue":0.0,
"defaultValueInMiddle":false,
"ratio":1
}
]

View File

@@ -0,0 +1,35 @@
//
// FUBeautyFilterView.h
// FUBeautyComponent
//
// Created by 项林平 on 2022/6/21.
//
#import <UIKit/UIKit.h>
#import "FUBeautyFilterViewModel.h"
NS_ASSUME_NONNULL_BEGIN
@protocol FUBeautyFilterViewDelegate <NSObject>
/// 滤镜变化
- (void)beautyFilterViewDidChangeFilter:(NSString *)name;
@end
@interface FUBeautyFilterView : UIView
@property (nonatomic, weak) id<FUBeautyFilterViewDelegate> delegate;
- (instancetype)initWithFrame:(CGRect)frame viewModel:(FUBeautyFilterViewModel *)viewModel;
@end
@interface FUBeautyFilterCell : UICollectionViewCell
@property (nonatomic, strong, readonly) UIImageView *imageView;
@property (nonatomic, strong, readonly) UILabel *textLabel;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,201 @@
//
// FUBeautyFilterView.m
// FUBeautyComponent
//
// Created by on 2022/6/21.
//
#import "FUBeautyFilterView.h"
#import "FUBeautyDefine.h"
#import <FUCommonUIComponent/FUSlider.h>
static NSString * const kFUBeautyFilterCellIdentifier = @"FUBeautyFilterCell";
@interface FUBeautyFilterView ()<UICollectionViewDataSource, UICollectionViewDelegate>
@property (nonatomic, strong) FUSlider *filterSlider;
@property (nonatomic, strong) UICollectionView *filterCollectionView;
@property (nonatomic, strong) FUBeautyFilterViewModel *viewModel;
@end
@implementation FUBeautyFilterView
#pragma mark - Initializer
- (instancetype)initWithFrame:(CGRect)frame {
return [self initWithFrame:frame viewModel:[[FUBeautyFilterViewModel alloc] init]];
}
- (instancetype)initWithFrame:(CGRect)frame viewModel:(FUBeautyFilterViewModel *)viewModel {
self = [super initWithFrame:frame];
if (self) {
self.viewModel = viewModel;
[self configureUI];
}
return self;
}
#pragma mark - UI
- (void)configureUI {
UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:effect];
effectView.frame = CGRectMake(0, 0, CGRectGetWidth(self.frame), CGRectGetHeight(self.frame));
[self addSubview:effectView];
[self addSubview:self.filterSlider];
[self addSubview:self.filterCollectionView];
NSLayoutConstraint *bottom = [NSLayoutConstraint constraintWithItem:self.filterCollectionView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeBottom multiplier:1 constant:0];
NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:self.filterCollectionView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeading multiplier:1 constant:0];
NSLayoutConstraint *trailing = [NSLayoutConstraint constraintWithItem:self.filterCollectionView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTrailing multiplier:1 constant:0];
NSLayoutConstraint *height = [NSLayoutConstraint constraintWithItem:self.filterCollectionView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:98];
[self addConstraints:@[bottom, leading, trailing]];
[self.filterCollectionView addConstraint:height];
//
[self.filterCollectionView selectItemAtIndexPath:[NSIndexPath indexPathForItem:self.viewModel.selectedIndex inSection:0] animated:YES scrollPosition:UICollectionViewScrollPositionCenteredHorizontally];
self.filterSlider.hidden = self.viewModel.selectedIndex < 1;
if (!self.filterSlider.hidden) {
self.filterSlider.value = self.viewModel.beautyFilters[self.viewModel.selectedIndex].filterLevel;
}
}
#pragma mark - Event response
- (void)sliderValueChanged {
[self.viewModel setFilterValue:self.filterSlider.value];
}
#pragma mark - Collection view data source
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.viewModel.beautyFilters.count;
}
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
FUBeautyFilterCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kFUBeautyFilterCellIdentifier forIndexPath:indexPath];
FUBeautyFilterModel *filter = self.viewModel.beautyFilters[indexPath.item];
cell.imageView.image = [UIImage imageNamed:filter.filterName];
cell.textLabel.text = FUBeautyStringWithKey(filter.filterName);
return cell;
}
#pragma mark - Collection view delegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
if (self.viewModel.selectedIndex == indexPath.item) {
return;
}
self.filterSlider.hidden = indexPath.item < 1;
if (!self.filterSlider.hidden) {
self.filterSlider.value = self.viewModel.beautyFilters[indexPath.item].filterLevel;
}
self.viewModel.selectedIndex = indexPath.item;
if (self.delegate && [self.delegate respondsToSelector:@selector(beautyFilterViewDidChangeFilter:)]) {
[self.delegate beautyFilterViewDidChangeFilter:FUBeautyStringWithKey(self.viewModel.beautyFilters[indexPath.item].filterName)];
}
}
#pragma mark - Getters
- (FUSlider *)filterSlider {
if (!_filterSlider) {
_filterSlider = [[FUSlider alloc] initWithFrame:CGRectMake(56, 16, CGRectGetWidth(self.frame) - 112, 30)];
_filterSlider.bidirection = NO;
_filterSlider.hidden = YES;
[_filterSlider addTarget:self action:@selector(sliderValueChanged) forControlEvents:UIControlEventValueChanged];
}
return _filterSlider;
}
- (UICollectionView *)filterCollectionView {
if (!_filterCollectionView) {
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
layout.itemSize = CGSizeMake(54, 70);
layout.minimumLineSpacing = 16;
layout.minimumInteritemSpacing = 50;
layout.sectionInset = UIEdgeInsetsMake(16, 18, 10, 18);
_filterCollectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
_filterCollectionView.translatesAutoresizingMaskIntoConstraints = NO;
_filterCollectionView.backgroundColor = [UIColor clearColor];
_filterCollectionView.showsHorizontalScrollIndicator = NO;
_filterCollectionView.dataSource = self;
_filterCollectionView.delegate = self;
[_filterCollectionView registerClass:[FUBeautyFilterCell class] forCellWithReuseIdentifier:kFUBeautyFilterCellIdentifier];
}
return _filterCollectionView;
}
- (FUBeautyFilterViewModel *)viewModel {
if (!_viewModel) {
_viewModel = [[FUBeautyFilterViewModel alloc] init];
}
return _viewModel;
}
@end
@interface FUBeautyFilterCell ()
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) UILabel *textLabel;
@end
@implementation FUBeautyFilterCell
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self.contentView addSubview:self.imageView];
NSLayoutConstraint *imageTop = [NSLayoutConstraint constraintWithItem:self.imageView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeTop multiplier:1 constant:0];
NSLayoutConstraint *imageLeading = [NSLayoutConstraint constraintWithItem:self.imageView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeLeading multiplier:1 constant:0];
NSLayoutConstraint *imageTrailing = [NSLayoutConstraint constraintWithItem:self.imageView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeTrailing multiplier:1 constant:0];
NSLayoutConstraint *imageHeight = [NSLayoutConstraint constraintWithItem:self.imageView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.imageView attribute:NSLayoutAttributeWidth multiplier:1 constant:0];
[self.contentView addConstraints:@[imageTop, imageLeading, imageTrailing]];
[self.imageView addConstraint:imageHeight];
[self.contentView addSubview:self.textLabel];
NSLayoutConstraint *textTop = [NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.imageView attribute:NSLayoutAttributeBottom multiplier:1 constant:2];
NSLayoutConstraint *textLeading = [NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeLeading multiplier:1 constant:0];
NSLayoutConstraint *textTrailing = [NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeTrailing multiplier:1 constant:0];
[self.contentView addConstraints:@[textTop, textLeading, textTrailing]];
}
return self;
}
- (void)setSelected:(BOOL)selected {
[super setSelected:selected];
self.imageView.layer.borderWidth = selected ? 2 : 0;
self.imageView.layer.borderColor = selected ? [UIColor colorWithRed:103/255.0 green:195/255.0 blue:103/255.0 alpha:1].CGColor : [UIColor clearColor].CGColor;
self.textLabel.textColor = selected ? [UIColor colorWithRed:103/255.0 green:195/255.0 blue:103/255.0 alpha:1] : [UIColor whiteColor];
}
- (UIImageView *)imageView {
if (!_imageView) {
_imageView = [[UIImageView alloc] init];
_imageView.translatesAutoresizingMaskIntoConstraints = NO;
_imageView.layer.masksToBounds = YES;
_imageView.layer.cornerRadius = 3.f;
}
return _imageView;
}
- (UILabel *)textLabel {
if (!_textLabel) {
_textLabel = [[UILabel alloc] init];
_textLabel.textColor = [UIColor whiteColor];
_textLabel.font = [UIFont systemFontOfSize:14];
_textLabel.textAlignment = NSTextAlignmentCenter;
_textLabel.adjustsFontSizeToFitWidth = YES;
_textLabel.translatesAutoresizingMaskIntoConstraints = NO;
}
return _textLabel;
}
@end

View File

@@ -0,0 +1,37 @@
//
// FUBeautyShapeView.h
// FUBeautyComponent
//
// Created by 项林平 on 2022/7/8.
//
#import <UIKit/UIKit.h>
#import "FUBeautyShapeViewModel.h"
NS_ASSUME_NONNULL_BEGIN
@interface FUBeautyShapeView : UIView
- (instancetype)initWithFrame:(CGRect)frame viewModel:(FUBeautyShapeViewModel *)viewModel;
@end
@interface FUBeautyShapeCell : UICollectionViewCell
@property (nonatomic, strong, readonly) UIImageView *imageView;
@property (nonatomic, strong, readonly) UILabel *textLabel;
/// 是否允许选择
@property (nonatomic, assign) BOOL disabled;
@property (nonatomic, assign) BOOL defaultInMiddle;
@property (nonatomic, assign) double currentValue;
@property (nonatomic, assign) double defaultValue;
@property (nonatomic, copy) NSString *imageName;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,299 @@
//
// FUBeautyShapeView.m
// FUBeautyComponent
//
// Created by on 2022/7/8.
//
#import "FUBeautyShapeView.h"
#import "FUBeautyDefine.h"
#import <FUCommonUIComponent/FUCommonUIComponent.h>
static NSString * const kFUBeautyShapeCellIdentifier = @"FUBeautyShapeCell";
@interface FUBeautyShapeView ()<UICollectionViewDataSource, UICollectionViewDelegate>
@property (nonatomic, strong) UICollectionView *shapeCollectionView;
///
@property (nonatomic, strong) FUSlider *slider;
///
@property (nonatomic, strong) FUSquareButton *recoverButton;
@property (nonatomic, strong) FUBeautyShapeViewModel *viewModel;
@end
@implementation FUBeautyShapeView
#pragma mark - Initializer
- (instancetype)initWithFrame:(CGRect)frame {
return [self initWithFrame:frame viewModel:[[FUBeautyShapeViewModel alloc] init]];
}
- (instancetype)initWithFrame:(CGRect)frame viewModel:(FUBeautyShapeViewModel *)viewModel {
self = [super initWithFrame:frame];
if (self) {
self.viewModel = viewModel;
[self configureUI];
[self refreshSubviews];
[self collectionView:self.shapeCollectionView didSelectItemAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
}
return self;
}
#pragma mark - UI
- (void)configureUI {
UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:effect];
effectView.frame = CGRectMake(0, 0, CGRectGetWidth(self.frame), CGRectGetHeight(self.frame));
[self addSubview:effectView];
[self addSubview:self.slider];
[self addSubview:self.recoverButton];
NSLayoutConstraint *recoverLeadingConstraint = [NSLayoutConstraint constraintWithItem:self.recoverButton attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeading multiplier:1 constant:17];
NSLayoutConstraint *recoverBottomConstraint = [NSLayoutConstraint constraintWithItem:self.recoverButton attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeBottom multiplier:1 constant:-6];
NSLayoutConstraint *recoverWidthConstraint = [NSLayoutConstraint constraintWithItem:self.recoverButton attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:44];
NSLayoutConstraint *recoverHeightConstraint = [NSLayoutConstraint constraintWithItem:self.recoverButton attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:74];
[self addConstraints:@[recoverLeadingConstraint, recoverBottomConstraint]];
[self.recoverButton addConstraints:@[recoverWidthConstraint, recoverHeightConstraint]];
// 线
UIView *verticalLine = [[UIView alloc] init];
verticalLine.backgroundColor = [UIColor colorWithRed:229/255.f green:229/255.f blue:229/255.f alpha:0.2];
verticalLine.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:verticalLine];
NSLayoutConstraint *lineLeadingConstraint = [NSLayoutConstraint constraintWithItem:verticalLine attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.recoverButton attribute:NSLayoutAttributeTrailing multiplier:1 constant:14];
NSLayoutConstraint *lineCenterYConstraint = [NSLayoutConstraint constraintWithItem:verticalLine attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.recoverButton attribute:NSLayoutAttributeCenterY multiplier:1 constant:-15];
NSLayoutConstraint *lineWidthConstraint = [NSLayoutConstraint constraintWithItem:verticalLine attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:1];
NSLayoutConstraint *lineHeightConstraint = [NSLayoutConstraint constraintWithItem:verticalLine attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:24];
[self addConstraints:@[lineLeadingConstraint, lineCenterYConstraint]];
[verticalLine addConstraints:@[lineWidthConstraint, lineHeightConstraint]];
[self addSubview:self.shapeCollectionView];
NSLayoutConstraint *collectionLeadingConstraint = [NSLayoutConstraint constraintWithItem:self.shapeCollectionView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeading multiplier:1 constant:76];
NSLayoutConstraint *collectionTrailingConstraint = [NSLayoutConstraint constraintWithItem:self.shapeCollectionView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTrailing multiplier:1 constant:0];
NSLayoutConstraint *collectionBottomConstraint = [NSLayoutConstraint constraintWithItem:self.shapeCollectionView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeBottom multiplier:1 constant:0];
NSLayoutConstraint *collectionHeightConstraint = [NSLayoutConstraint constraintWithItem:self.shapeCollectionView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:98];
[self addConstraints:@[collectionLeadingConstraint, collectionTrailingConstraint, collectionBottomConstraint]];
[self.shapeCollectionView addConstraint:collectionHeightConstraint];
}
- (void)refreshSubviews {
dispatch_async(dispatch_get_main_queue(), ^{
if (self.viewModel.isDefaultValue) {
self.recoverButton.alpha = 0.6;
self.recoverButton.userInteractionEnabled = NO;
} else {
self.recoverButton.alpha = 1;
self.recoverButton.userInteractionEnabled = YES;
}
if (!self.slider.hidden && self.viewModel.selectedIndex >= 0) {
self.slider.bidirection = self.viewModel.beautyShapes[self.viewModel.selectedIndex].defaultValueInMiddle;
self.slider.value = self.viewModel.beautyShapes[self.viewModel.selectedIndex].currentValue;
}
[self.shapeCollectionView reloadData];
if (self.viewModel.selectedIndex >= 0) {
[self.shapeCollectionView selectItemAtIndexPath:[NSIndexPath indexPathForItem:self.viewModel.selectedIndex inSection:0] animated:NO scrollPosition:UICollectionViewScrollPositionNone];
}
});
}
#pragma mark - Event response
- (void)recoverAction {
[FUAlertManager showAlertWithTitle:nil message:FUBeautyStringWithKey(@"是否将所有参数恢复到默认值") cancel:FUBeautyStringWithKey(@"取消") confirm:FUBeautyStringWithKey(@"确定") inController:nil confirmHandler:^{
[self.viewModel recoverAllShapeValuesToDefault];
[self refreshSubviews];
} cancelHandler:nil];
}
- (void)sliderValueChanged {
[self.viewModel setShapeValue:self.slider.value];
}
- (void)sliderChangeEnded {
[self refreshSubviews];
}
#pragma mark - Collection view data source
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.viewModel.beautyShapes.count;
}
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
FUBeautyShapeCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kFUBeautyShapeCellIdentifier forIndexPath:indexPath];
FUBeautyShapeModel *shape = self.viewModel.beautyShapes[indexPath.item];
cell.textLabel.text = FUBeautyStringWithKey(shape.name);
cell.imageName = shape.name;
cell.defaultInMiddle = shape.defaultValueInMiddle;
cell.defaultValue = shape.defaultValue;
cell.currentValue = shape.currentValue;
//
if (shape.differentiateDevicePerformance) {
cell.disabled = self.viewModel.performanceLevel != FUDevicePerformanceLevelHigh;
} else {
cell.disabled = NO;
}
cell.selected = indexPath.item == self.viewModel.selectedIndex;
return cell;
}
#pragma mark - Collection view delegate
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath {
FUBeautyShapeModel *shape = self.viewModel.beautyShapes[indexPath.item];
if (shape.differentiateDevicePerformance) {
if (self.viewModel.performanceLevel != FUDevicePerformanceLevelHigh) {
[FUTipHUD showTips:[NSString stringWithFormat:FUBeautyStringWithKey(@"该功能只支持在高端机上使用"), FUBeautyStringWithKey(shape.name)] dismissWithDelay:1];
[self.shapeCollectionView reloadData];
if (self.viewModel.selectedIndex >= 0) {
[self.shapeCollectionView selectItemAtIndexPath:[NSIndexPath indexPathForItem:self.viewModel.selectedIndex inSection:0] animated:NO scrollPosition:UICollectionViewScrollPositionNone];
}
return NO;
}
}
return YES;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.item == self.viewModel.selectedIndex) {
return;
}
self.viewModel.selectedIndex = indexPath.item;
FUBeautyShapeModel *shape = self.viewModel.beautyShapes[indexPath.item];
if (self.slider.hidden) {
self.slider.hidden = NO;
}
self.slider.bidirection = shape.defaultValueInMiddle;
self.slider.value = shape.currentValue;
}
#pragma mark - Getters
- (UICollectionView *)shapeCollectionView {
if (!_shapeCollectionView) {
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
layout.itemSize = CGSizeMake(44, 74);
layout.minimumLineSpacing = 22;
layout.minimumInteritemSpacing = 22;
layout.sectionInset = UIEdgeInsetsMake(16, 16, 6, 16);
_shapeCollectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
_shapeCollectionView.translatesAutoresizingMaskIntoConstraints = NO;
_shapeCollectionView.backgroundColor = [UIColor clearColor];
_shapeCollectionView.showsVerticalScrollIndicator = NO;
_shapeCollectionView.showsHorizontalScrollIndicator = NO;
_shapeCollectionView.dataSource = self;
_shapeCollectionView.delegate = self;
[_shapeCollectionView registerClass:[FUBeautyShapeCell class] forCellWithReuseIdentifier:kFUBeautyShapeCellIdentifier];
}
return _shapeCollectionView;
}
- (FUSquareButton *)recoverButton {
if (!_recoverButton) {
_recoverButton = [[FUSquareButton alloc] initWithFrame:CGRectMake(0, 0, 44, 74)];
[_recoverButton setTitle:FUBeautyStringWithKey(@"恢复") forState:UIControlStateNormal];
[_recoverButton setImage:[UIImage imageNamed:@"recover_item"] forState:UIControlStateNormal];
_recoverButton.alpha = 0.6;
_recoverButton.userInteractionEnabled = NO;
[_recoverButton addTarget:self action:@selector(recoverAction) forControlEvents:UIControlEventTouchUpInside];
_recoverButton.translatesAutoresizingMaskIntoConstraints = NO;
}
return _recoverButton;
}
-(FUSlider *)slider {
if (!_slider) {
_slider = [[FUSlider alloc] initWithFrame:CGRectMake(56, 16, CGRectGetWidth(self.frame) - 116, FUBeautyFunctionSliderHeight)];
_slider.hidden = YES;
[_slider addTarget:self action:@selector(sliderValueChanged) forControlEvents:UIControlEventValueChanged];
[_slider addTarget:self action:@selector(sliderChangeEnded) forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchUpOutside];
}
return _slider;
}
@end
@interface FUBeautyShapeCell ()
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) UILabel *textLabel;
@end
@implementation FUBeautyShapeCell
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self.contentView addSubview:self.imageView];
NSLayoutConstraint *imageTop = [NSLayoutConstraint constraintWithItem:self.imageView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeTop multiplier:1 constant:0];
NSLayoutConstraint *imageLeading = [NSLayoutConstraint constraintWithItem:self.imageView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeLeading multiplier:1 constant:0];
NSLayoutConstraint *imageTrailing = [NSLayoutConstraint constraintWithItem:self.imageView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeTrailing multiplier:1 constant:0];
NSLayoutConstraint *imageHeight = [NSLayoutConstraint constraintWithItem:self.imageView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.imageView attribute:NSLayoutAttributeWidth multiplier:1 constant:0];
[self.contentView addConstraints:@[imageTop, imageLeading, imageTrailing]];
[self.imageView addConstraint:imageHeight];
[self.contentView addSubview:self.textLabel];
NSLayoutConstraint *textTop = [NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.imageView attribute:NSLayoutAttributeBottom multiplier:1 constant:7];
NSLayoutConstraint *textLeading = [NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeLeading multiplier:1 constant:0];
NSLayoutConstraint *textTrailing = [NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeTrailing multiplier:1 constant:0];
[self.contentView addConstraints:@[textTop, textLeading, textTrailing]];
}
return self;
}
- (void)setSelected:(BOOL)selected {
[super setSelected:selected];
if (self.disabled) {
self.imageView.image = [UIImage imageNamed:[NSString stringWithFormat:@"%@-0", self.imageName]];
self.imageView.alpha = 0.7;
self.textLabel.alpha = 0.7;
} else {
self.imageView.alpha = 1;
self.textLabel.alpha = 1;
BOOL changed = NO;
if (self.defaultInMiddle) {
changed = fabs(self.currentValue - 0.5) > 0.01;
}else{
changed = self.currentValue > 0.01;
}
if (selected) {
self.imageView.image = changed ? [UIImage imageNamed:[NSString stringWithFormat:@"%@-3", self.imageName]] : [UIImage imageNamed:[NSString stringWithFormat:@"%@-2", self.imageName]];
self.textLabel.textColor = [UIColor colorWithRed:103/255.f green:195/255.f blue:103/255.f alpha:1];
} else {
self.imageView.image = changed ? [UIImage imageNamed:[NSString stringWithFormat:@"%@-1", self.imageName]] : [UIImage imageNamed:[NSString stringWithFormat:@"%@-0", self.imageName]];
self.textLabel.textColor = [UIColor whiteColor];
}
}
}
- (UIImageView *)imageView {
if (!_imageView) {
_imageView = [[UIImageView alloc] init];
_imageView.translatesAutoresizingMaskIntoConstraints = NO;
}
return _imageView;
}
- (UILabel *)textLabel {
if (!_textLabel) {
_textLabel = [[UILabel alloc] init];
_textLabel.font = [UIFont systemFontOfSize:14];
_textLabel.textColor = [UIColor whiteColor];
_textLabel.textAlignment = NSTextAlignmentCenter;
_textLabel.adjustsFontSizeToFitWidth = YES;
_textLabel.translatesAutoresizingMaskIntoConstraints = NO;
}
return _textLabel;
}
@end

View File

@@ -0,0 +1,36 @@
//
// FUBeautySkinView.h
// FUBeautyComponent
//
// Created by 项林平 on 2022/6/21.
//
// 美肤&美型视图
#import <UIKit/UIKit.h>
#import "FUBeautySkinViewModel.h"
NS_ASSUME_NONNULL_BEGIN
@interface FUBeautySkinView : UIView
- (instancetype)initWithFrame:(CGRect)frame viewModel:(FUBeautySkinViewModel *)viewModel;
@end
@interface FUBeautySkinCell : UICollectionViewCell
@property (nonatomic, strong, readonly) UIImageView *imageView;
@property (nonatomic, strong, readonly) UILabel *textLabel;
@property (nonatomic, assign) BOOL defaultInMiddle;
@property (nonatomic, assign) double currentValue;
@property (nonatomic, assign) double defaultValue;
@property (nonatomic, copy) NSString *imageName;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,269 @@
//
// FUBeautySkinView.m
// FUBeautyComponent
//
// Created by on 2022/6/21.
//
#import "FUBeautySkinView.h"
#import "FUBeautyDefine.h"
#import <FUCommonUIComponent/FUCommonUIComponent.h>
static NSString * const kFUBeautySkinCellIdentifier = @"FUBeautySkinCell";
@interface FUBeautySkinView ()<UICollectionViewDataSource, UICollectionViewDelegate>
@property (nonatomic, strong) UICollectionView *skinCollectionView;
///
@property (nonatomic, strong) FUSlider *slider;
///
@property (nonatomic, strong) FUSquareButton *recoverButton;
@property (nonatomic, strong) FUBeautySkinViewModel *viewModel;
@end
@implementation FUBeautySkinView
#pragma mark - Initializer
- (instancetype)initWithFrame:(CGRect)frame {
return [self initWithFrame:frame viewModel:[[FUBeautySkinViewModel alloc] init]];
}
- (instancetype)initWithFrame:(CGRect)frame viewModel:(FUBeautySkinViewModel *)viewModel {
self = [super initWithFrame:frame];
if (self) {
self.viewModel = viewModel;
[self configureUI];
[self refreshSubviews];
[self collectionView:self.skinCollectionView didSelectItemAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
}
return self;
}
#pragma mark - UI
- (void)configureUI {
UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:effect];
effectView.frame = CGRectMake(0, 0, CGRectGetWidth(self.frame), CGRectGetHeight(self.frame));
[self addSubview:effectView];
[self addSubview:self.slider];
[self addSubview:self.recoverButton];
NSLayoutConstraint *recoverLeadingConstraint = [NSLayoutConstraint constraintWithItem:self.recoverButton attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeading multiplier:1 constant:17];
NSLayoutConstraint *recoverBottomConstraint = [NSLayoutConstraint constraintWithItem:self.recoverButton attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeBottom multiplier:1 constant:-6];
NSLayoutConstraint *recoverWidthConstraint = [NSLayoutConstraint constraintWithItem:self.recoverButton attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:44];
NSLayoutConstraint *recoverHeightConstraint = [NSLayoutConstraint constraintWithItem:self.recoverButton attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:74];
[self addConstraints:@[recoverLeadingConstraint, recoverBottomConstraint]];
[self.recoverButton addConstraints:@[recoverWidthConstraint, recoverHeightConstraint]];
// 线
UIView *verticalLine = [[UIView alloc] init];
verticalLine.backgroundColor = [UIColor colorWithRed:229/255.f green:229/255.f blue:229/255.f alpha:0.2];
verticalLine.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:verticalLine];
NSLayoutConstraint *lineLeadingConstraint = [NSLayoutConstraint constraintWithItem:verticalLine attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.recoverButton attribute:NSLayoutAttributeTrailing multiplier:1 constant:14];
NSLayoutConstraint *lineCenterYConstraint = [NSLayoutConstraint constraintWithItem:verticalLine attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.recoverButton attribute:NSLayoutAttributeCenterY multiplier:1 constant:-15];
NSLayoutConstraint *lineWidthConstraint = [NSLayoutConstraint constraintWithItem:verticalLine attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:1];
NSLayoutConstraint *lineHeightConstraint = [NSLayoutConstraint constraintWithItem:verticalLine attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:24];
[self addConstraints:@[lineLeadingConstraint, lineCenterYConstraint]];
[verticalLine addConstraints:@[lineWidthConstraint, lineHeightConstraint]];
[self addSubview:self.skinCollectionView];
NSLayoutConstraint *collectionLeadingConstraint = [NSLayoutConstraint constraintWithItem:self.skinCollectionView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeading multiplier:1 constant:76];
NSLayoutConstraint *collectionTrailingConstraint = [NSLayoutConstraint constraintWithItem:self.skinCollectionView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTrailing multiplier:1 constant:0];
NSLayoutConstraint *collectionBottomConstraint = [NSLayoutConstraint constraintWithItem:self.skinCollectionView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeBottom multiplier:1 constant:0];
NSLayoutConstraint *collectionHeightConstraint = [NSLayoutConstraint constraintWithItem:self.skinCollectionView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:98];
[self addConstraints:@[collectionLeadingConstraint, collectionTrailingConstraint, collectionBottomConstraint]];
[self.skinCollectionView addConstraint:collectionHeightConstraint];
}
- (void)refreshSubviews {
dispatch_async(dispatch_get_main_queue(), ^{
if (self.viewModel.isDefaultValue) {
self.recoverButton.alpha = 0.6;
self.recoverButton.userInteractionEnabled = NO;
} else {
self.recoverButton.alpha = 1;
self.recoverButton.userInteractionEnabled = YES;
}
if (!self.slider.hidden && self.viewModel.selectedIndex >= 0) {
self.slider.bidirection = self.viewModel.beautySkins[self.viewModel.selectedIndex].defaultValueInMiddle;
self.slider.value = self.viewModel.beautySkins[self.viewModel.selectedIndex].currentValue / self.viewModel.beautySkins[self.viewModel.selectedIndex].ratio;
}
[self.skinCollectionView reloadData];
if (self.viewModel.selectedIndex >= 0) {
[self.skinCollectionView selectItemAtIndexPath:[NSIndexPath indexPathForItem:self.viewModel.selectedIndex inSection:0] animated:NO scrollPosition:UICollectionViewScrollPositionNone];
}
});
}
#pragma mark - Event response
- (void)recoverAction {
[FUAlertManager showAlertWithTitle:nil message:FUBeautyStringWithKey(@"是否将所有参数恢复到默认值") cancel:FUBeautyStringWithKey(@"取消") confirm:FUBeautyStringWithKey(@"确定") inController:nil confirmHandler:^{
[self.viewModel recoverAllSkinValuesToDefault];
[self refreshSubviews];
} cancelHandler:nil];
}
- (void)sliderValueChanged {
[self.viewModel setSkinValue:self.slider.value];
}
- (void)sliderChangeEnded {
[self refreshSubviews];
}
#pragma mark - Collection view data source
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.viewModel.beautySkins.count;
}
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
FUBeautySkinCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kFUBeautySkinCellIdentifier forIndexPath:indexPath];
FUBeautySkinModel *skin = self.viewModel.beautySkins[indexPath.item];
cell.textLabel.text = FUBeautyStringWithKey(skin.name);
cell.imageName = skin.name;
cell.defaultInMiddle = skin.defaultValueInMiddle;
cell.defaultValue = skin.defaultValue;
cell.currentValue = skin.currentValue;
cell.selected = indexPath.item == self.viewModel.selectedIndex;
return cell;
}
#pragma mark - Collection view delegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.item == self.viewModel.selectedIndex) {
return;
}
self.viewModel.selectedIndex = indexPath.item;
FUBeautySkinModel *skin = self.viewModel.beautySkins[indexPath.item];
if (self.slider.hidden) {
self.slider.hidden = NO;
}
self.slider.bidirection = skin.defaultValueInMiddle;
self.slider.value = skin.currentValue / skin.ratio;
}
#pragma mark - Getters
- (UICollectionView *)skinCollectionView {
if (!_skinCollectionView) {
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
layout.itemSize = CGSizeMake(44, 74);
layout.minimumLineSpacing = 22;
layout.minimumInteritemSpacing = 22;
layout.sectionInset = UIEdgeInsetsMake(16, 16, 6, 16);
_skinCollectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
_skinCollectionView.translatesAutoresizingMaskIntoConstraints = NO;
_skinCollectionView.backgroundColor = [UIColor clearColor];
_skinCollectionView.showsVerticalScrollIndicator = NO;
_skinCollectionView.showsHorizontalScrollIndicator = NO;
_skinCollectionView.dataSource = self;
_skinCollectionView.delegate = self;
[_skinCollectionView registerClass:[FUBeautySkinCell class] forCellWithReuseIdentifier:kFUBeautySkinCellIdentifier];
}
return _skinCollectionView;
}
- (FUSquareButton *)recoverButton {
if (!_recoverButton) {
_recoverButton = [[FUSquareButton alloc] initWithFrame:CGRectMake(0, 0, 44, 74)];
[_recoverButton setTitle:FUBeautyStringWithKey(@"恢复") forState:UIControlStateNormal];
[_recoverButton setImage:[UIImage imageNamed:@"recover_item"] forState:UIControlStateNormal];
_recoverButton.alpha = 0.6;
_recoverButton.userInteractionEnabled = NO;
[_recoverButton addTarget:self action:@selector(recoverAction) forControlEvents:UIControlEventTouchUpInside];
_recoverButton.translatesAutoresizingMaskIntoConstraints = NO;
}
return _recoverButton;
}
-(FUSlider *)slider {
if (!_slider) {
_slider = [[FUSlider alloc] initWithFrame:CGRectMake(56, 16, CGRectGetWidth(self.frame) - 116, FUBeautyFunctionSliderHeight)];
_slider.hidden = YES;//67C367
[_slider addTarget:self action:@selector(sliderValueChanged) forControlEvents:UIControlEventValueChanged];
[_slider addTarget:self action:@selector(sliderChangeEnded) forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchUpOutside];
}
return _slider;
}
@end
@interface FUBeautySkinCell ()
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) UILabel *textLabel;
@end
@implementation FUBeautySkinCell
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self.contentView addSubview:self.imageView];
NSLayoutConstraint *imageTop = [NSLayoutConstraint constraintWithItem:self.imageView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeTop multiplier:1 constant:0];
NSLayoutConstraint *imageLeading = [NSLayoutConstraint constraintWithItem:self.imageView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeLeading multiplier:1 constant:0];
NSLayoutConstraint *imageTrailing = [NSLayoutConstraint constraintWithItem:self.imageView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeTrailing multiplier:1 constant:0];
NSLayoutConstraint *imageHeight = [NSLayoutConstraint constraintWithItem:self.imageView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.imageView attribute:NSLayoutAttributeWidth multiplier:1 constant:0];
[self.contentView addConstraints:@[imageTop, imageLeading, imageTrailing]];
[self.imageView addConstraint:imageHeight];
[self.contentView addSubview:self.textLabel];
NSLayoutConstraint *textTop = [NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.imageView attribute:NSLayoutAttributeBottom multiplier:1 constant:7];
NSLayoutConstraint *textLeading = [NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeLeading multiplier:1 constant:0];
NSLayoutConstraint *textTrailing = [NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeTrailing multiplier:1 constant:0];
[self.contentView addConstraints:@[textTop, textLeading, textTrailing]];
}
return self;
}
- (void)setSelected:(BOOL)selected {
[super setSelected:selected];
BOOL changed = NO;
if (self.defaultInMiddle) {
changed = fabs(self.currentValue - 0.5) > 0.01;
}else{
changed = self.currentValue > 0.01;
}
if (selected) {
self.imageView.image = changed ? [UIImage imageNamed:[NSString stringWithFormat:@"%@-3", self.imageName]] : [UIImage imageNamed:[NSString stringWithFormat:@"%@-2", self.imageName]];
self.textLabel.textColor = [UIColor colorWithRed:103/255.f green:195/255.f blue:103/255.f alpha:1];
} else {
self.imageView.image = changed ? [UIImage imageNamed:[NSString stringWithFormat:@"%@-1", self.imageName]] : [UIImage imageNamed:[NSString stringWithFormat:@"%@-0", self.imageName]];
self.textLabel.textColor = [UIColor whiteColor];
}
}
- (UIImageView *)imageView {
if (!_imageView) {
_imageView = [[UIImageView alloc] init];
_imageView.translatesAutoresizingMaskIntoConstraints = NO;
}
return _imageView;
}
- (UILabel *)textLabel {
if (!_textLabel) {
_textLabel = [[UILabel alloc] init];
_textLabel.font = [UIFont systemFontOfSize:14];
_textLabel.textColor = [UIColor whiteColor];
_textLabel.textAlignment = NSTextAlignmentCenter;
_textLabel.adjustsFontSizeToFitWidth = YES;
_textLabel.translatesAutoresizingMaskIntoConstraints = NO;
}
return _textLabel;
}
@end

View File

@@ -0,0 +1,31 @@
//
// FUBeautyFilterViewModel.h
// FUBeautyComponent
//
// Created by 项林平 on 2022/7/7.
//
#import <Foundation/Foundation.h>
#import "FUBeautyFilterModel.h"
NS_ASSUME_NONNULL_BEGIN
@interface FUBeautyFilterViewModel : NSObject
@property (nonatomic, copy, readonly) NSArray<FUBeautyFilterModel *> *beautyFilters;
/// 选中滤镜索引
@property (nonatomic, assign) NSInteger selectedIndex;
/// 保存滤镜数据到本地
- (void)saveFitersPersistently;
/// 设置当前滤镜
- (void)setCurrentFilter;
/// 设置滤镜程度
/// @param value 程度值
- (void)setFilterValue:(double)value;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,118 @@
//
// FUBeautyFilterViewModel.m
// FUBeautyComponent
//
// Created by on 2022/7/7.
//
#import "FUBeautyFilterViewModel.h"
#import "FUBeautyDefine.h"
#import <FURenderKit/FURenderKit.h>
@interface FUBeautyFilterViewModel ()
@property (nonatomic, copy) NSArray<FUBeautyFilterModel *> *beautyFilters;
@end
@implementation FUBeautyFilterViewModel
#pragma mark - Initializer
- (instancetype)init {
self = [super init];
if (self) {
if ([[NSUserDefaults standardUserDefaults] objectForKey:FUPersistentBeautyFilterKey]) {
//
self.beautyFilters = [self localFilters];
} else {
//
self.beautyFilters = [self defaultFilters];
}
if ([[NSUserDefaults standardUserDefaults] objectForKey:FUPersistentBeautySelectedFilterIndexKey]) {
//
_selectedIndex = [[NSUserDefaults standardUserDefaults] integerForKey:FUPersistentBeautySelectedFilterIndexKey];
} else {
// 2
_selectedIndex = 2;
}
}
return self;
}
#pragma mark - Instance methods
- (void)saveFitersPersistently {
if (self.beautyFilters.count == 0) {
return;
}
NSMutableArray *filters = [[NSMutableArray alloc] init];
for (FUBeautyFilterModel *model in self. beautyFilters) {
NSDictionary *dictionary = [model dictionaryWithValuesForKeys:@[@"filterIndex", @"filterName", @"filterLevel"]];
[filters addObject:dictionary];
}
[[NSUserDefaults standardUserDefaults] setObject:filters forKey:FUPersistentBeautyFilterKey];
[[NSUserDefaults standardUserDefaults] setInteger:self.selectedIndex forKey:FUPersistentBeautySelectedFilterIndexKey];
[[NSUserDefaults standardUserDefaults] synchronize];
}
- (void)setSelectedIndex:(NSInteger)selectedIndex {
if (selectedIndex < 0 || selectedIndex >= self.beautyFilters.count) {
return;
}
[self setFilter:self.beautyFilters[selectedIndex].filterName level:self.beautyFilters[selectedIndex].filterLevel];
_selectedIndex = selectedIndex;
}
- (void)setCurrentFilter {
if (self.selectedIndex < 0 || self.selectedIndex >= self.beautyFilters.count) {
return;
}
[self setFilter:self.beautyFilters[self.selectedIndex].filterName level:self.beautyFilters[self.selectedIndex].filterLevel];
}
- (void)setFilterValue:(double)value {
if (self.selectedIndex < 0 || self.selectedIndex >= self.beautyFilters.count) {
return;
}
FUBeautyFilterModel *model = self.beautyFilters[self.selectedIndex];
model.filterLevel = value;
[FURenderKit shareRenderKit].beauty.filterLevel = model.filterLevel;
}
#pragma mark - Private methods
- (void)setFilter:(NSString *)filterName level:(double)filterLevel {
[FURenderKit shareRenderKit].beauty.filterName = filterName;
[FURenderKit shareRenderKit].beauty.filterLevel = filterLevel;
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[self performSelector:@selector(saveFitersPersistently) withObject:nil afterDelay:2];
}
- (NSArray<FUBeautyFilterModel *> *)localFilters {
NSArray *filters = [[NSUserDefaults standardUserDefaults] objectForKey:FUPersistentBeautyFilterKey];
NSMutableArray *mutableFilters = [[NSMutableArray alloc] init];
for (NSDictionary *filter in filters) {
FUBeautyFilterModel *model = [[FUBeautyFilterModel alloc] init];
[model setValuesForKeysWithDictionary:filter];
[mutableFilters addObject:model];
}
return [mutableFilters copy];
}
- (NSArray<FUBeautyFilterModel *> *)defaultFilters {
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSString *filterPath = [bundle pathForResource:@"beauty_filter" ofType:@"json"];
NSArray<NSDictionary *> *filterData = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfFile:filterPath] options:NSJSONReadingMutableContainers error:nil];
NSMutableArray *filters = [[NSMutableArray alloc] init];
for (NSDictionary *dictionary in filterData) {
FUBeautyFilterModel *model = [[FUBeautyFilterModel alloc] init];
[model setValuesForKeysWithDictionary:dictionary];
[filters addObject:model];
}
return [filters copy];
}
@end

View File

@@ -0,0 +1,39 @@
//
// FUBeautyShapeViewModel.h
// FUBeautyComponent
//
// Created by 项林平 on 2022/7/27.
//
#import <Foundation/Foundation.h>
#import <FURenderKit/FURenderKit.h>
#import "FUBeautyShapeModel.h"
NS_ASSUME_NONNULL_BEGIN
@interface FUBeautyShapeViewModel : NSObject
@property (nonatomic, copy, readonly) NSArray<FUBeautyShapeModel *> *beautyShapes;
/// 是否所有值都是默认
@property (nonatomic, assign, readonly) BOOL isDefaultValue;
/// 当前选中索引,默认为-1
@property (nonatomic, assign) NSInteger selectedIndex;
/// 美型属性需要根据高低端机适配
@property (nonatomic, assign) FUDevicePerformanceLevel performanceLevel;
/// 保存美型数据到本地
- (void)saveShapesPersistently;
/// 设置单项美型值
/// @param value 当前选中单项的值
- (void)setShapeValue:(double)value;
/// 设置当前所有美型值
- (void)setAllShapeValues;
/// 恢复所有美型值为默认
- (void)recoverAllShapeValuesToDefault;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,197 @@
//
// FUBeautyShapeViewModel.m
// FUBeautyComponent
//
// Created by on 2022/7/27.
//
#import "FUBeautyShapeViewModel.h"
#import "FUBeautyDefine.h"
#import <FURenderKit/FURenderKit.h>
@interface FUBeautyShapeViewModel ()
@property (nonatomic, copy) NSArray<FUBeautyShapeModel *> *beautyShapes;
@end
@implementation FUBeautyShapeViewModel
#pragma mark - Initializer
- (instancetype)init {
self = [super init];
if (self) {
if ([[NSUserDefaults standardUserDefaults] objectForKey:FUPersistentBeautyShapeKey]) {
//
self.beautyShapes = [self localShapes];
} else {
//
self.beautyShapes = [self defaultShapes];
}
self.selectedIndex = -1;
self.performanceLevel = [FURenderKit devicePerformanceLevel];
}
return self;
}
#pragma mark - Instance methods
- (void)saveShapesPersistently {
if (self.beautyShapes.count == 0) {
return;
}
NSMutableArray *shapes = [[NSMutableArray alloc] init];
for (FUBeautyShapeModel *model in self.beautyShapes) {
NSDictionary *dictionary = [model dictionaryWithValuesForKeys:@[@"name", @"type", @"currentValue", @"defaultValue", @"defaultValueInMiddle", @"differentiateDevicePerformance"]];
[shapes addObject:dictionary];
}
[[NSUserDefaults standardUserDefaults] setObject:shapes forKey:FUPersistentBeautyShapeKey];
[[NSUserDefaults standardUserDefaults] synchronize];
}
- (void)setShapeValue:(double)value {
if (self.selectedIndex < 0 || self.selectedIndex >= self.beautyShapes.count) {
return;
}
FUBeautyShapeModel *model = self.beautyShapes[self.selectedIndex];
model.currentValue = value;
[self setValue:model.currentValue forType:model.type];
}
- (void)setAllShapeValues {
for (FUBeautyShapeModel *shape in self.beautyShapes) {
[self setValue:shape.currentValue forType:shape.type];
}
}
- (void)recoverAllShapeValuesToDefault {
for (FUBeautyShapeModel *shape in self.beautyShapes) {
shape.currentValue = shape.defaultValue;
[self setValue:shape.currentValue forType:shape.type];
}
}
#pragma mark - Private methods
- (void)setValue:(double)value forType:(FUBeautyShape)type {
switch (type) {
case FUBeautyShapeCheekThinning:
[FURenderKit shareRenderKit].beauty.cheekThinning = value;
break;
case FUBeautyShapeCheekV:
[FURenderKit shareRenderKit].beauty.cheekV = value;
break;
case FUBeautyShapeCheekNarrow:
[FURenderKit shareRenderKit].beauty.cheekNarrow = value;
break;
case FUBeautyShapeCheekShort:
[FURenderKit shareRenderKit].beauty.cheekShort = value;
break;
case FUBeautyShapeCheekSmall:
[FURenderKit shareRenderKit].beauty.cheekSmall = value;
break;
case FUBeautyShapeCheekbones:
[FURenderKit shareRenderKit].beauty.intensityCheekbones = value;
break;
case FUBeautyShapeLowerJaw:
[FURenderKit shareRenderKit].beauty.intensityLowerJaw = value;
break;
case FUBeautyShapeEyeEnlarging:
[FURenderKit shareRenderKit].beauty.eyeEnlarging = value;
break;
case FUBeautyShapeEyeCircle:
[FURenderKit shareRenderKit].beauty.intensityEyeCircle = value;
break;
case FUBeautyShapeChin:
[FURenderKit shareRenderKit].beauty.intensityChin = value;
break;
case FUBeautyShapeForehead:
[FURenderKit shareRenderKit].beauty.intensityForehead = value;
break;
case FUBeautyShapeNose:
[FURenderKit shareRenderKit].beauty.intensityNose = value;
break;
case FUBeautyShapeMouth:
[FURenderKit shareRenderKit].beauty.intensityMouth = value;
break;
case FUBeautyShapeLipThick:
[FURenderKit shareRenderKit].beauty.intensityLipThick = value;
break;
case FUBeautyShapeEyeHeight:
[FURenderKit shareRenderKit].beauty.intensityEyeHeight = value;
break;
case FUBeautyShapeCanthus:
[FURenderKit shareRenderKit].beauty.intensityCanthus = value;
break;
case FUBeautyShapeEyeLid:
[FURenderKit shareRenderKit].beauty.intensityEyeLid = value;
break;
case FUBeautyShapeEyeSpace:
[FURenderKit shareRenderKit].beauty.intensityEyeSpace = value;
break;
case FUBeautyShapeEyeRotate:
[FURenderKit shareRenderKit].beauty.intensityEyeRotate = value;
break;
case FUBeautyShapeLongNose:
[FURenderKit shareRenderKit].beauty.intensityLongNose = value;
break;
case FUBeautyShapePhiltrum:
[FURenderKit shareRenderKit].beauty.intensityPhiltrum = value;
break;
case FUBeautyShapeSmile:
[FURenderKit shareRenderKit].beauty.intensitySmile = value;
break;
case FUBeautyShapeBrowHeight:
[FURenderKit shareRenderKit].beauty.intensityBrowHeight = value;
break;
case FUBeautyShapeBrowSpace:
[FURenderKit shareRenderKit].beauty.intensityBrowSpace = value;
break;
case FUBeautyShapeBrowThick:
[FURenderKit shareRenderKit].beauty.intensityBrowThick = value;
break;
}
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[self performSelector:@selector(saveShapesPersistently) withObject:nil afterDelay:2];
}
- (NSArray<FUBeautyShapeModel *> *)localShapes {
NSArray *shapes = [[NSUserDefaults standardUserDefaults] objectForKey:FUPersistentBeautyShapeKey];
NSMutableArray *mutableShapes = [[NSMutableArray alloc] init];
for (NSDictionary *shape in shapes) {
FUBeautyShapeModel *model = [[FUBeautyShapeModel alloc] init];
[model setValuesForKeysWithDictionary:shape];
[mutableShapes addObject:model];
}
return [mutableShapes copy];
}
- (NSArray<FUBeautyShapeModel *> *)defaultShapes {
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSString *shapePath = [bundle pathForResource:@"beauty_shape" ofType:@"json"];
NSArray<NSDictionary *> *shapeData = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfFile:shapePath] options:NSJSONReadingMutableContainers error:nil];
NSMutableArray *shapes = [[NSMutableArray alloc] init];
for (NSDictionary *dictionary in shapeData) {
FUBeautyShapeModel *model = [[FUBeautyShapeModel alloc] init];
[model setValuesForKeysWithDictionary:dictionary];
[shapes addObject:model];
}
return [shapes copy];
}
#pragma mark - Getters
- (BOOL)isDefaultValue {
for (FUBeautyShapeModel *shape in self.beautyShapes) {
int currentIntValue = shape.defaultValueInMiddle ? (int)(shape.currentValue * 100 - 50) : (int)(shape.currentValue * 100);
int defaultIntValue = shape.defaultValueInMiddle ? (int)(shape.defaultValue * 100 - 50) : (int)(shape.defaultValue * 100);
if (currentIntValue != defaultIntValue) {
return NO;
}
}
return YES;
}
@end

View File

@@ -0,0 +1,36 @@
//
// FUBeautySkinViewModel.h
// FUBeautyComponent
//
// Created by 项林平 on 2022/7/27.
//
#import <Foundation/Foundation.h>
#import "FUBeautySkinModel.h"
NS_ASSUME_NONNULL_BEGIN
@interface FUBeautySkinViewModel : NSObject
@property (nonatomic, copy, readonly) NSArray<FUBeautySkinModel *> *beautySkins;
/// 是否所有值都是默认
@property (nonatomic, assign, readonly) BOOL isDefaultValue;
/// 当前选中索引,默认为-1
@property (nonatomic, assign) NSInteger selectedIndex;
/// 保存美肤数据到本地
- (void)saveSkinsPersistently;
/// 设置单项美肤值
/// @param value 当前选中单项的值
- (void)setSkinValue:(double)value;
/// 设置当前所有美肤值
- (void)setAllSkinValues;
/// 恢复所有美肤值为默认
- (void)recoverAllSkinValuesToDefault;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,148 @@
//
// FUBeautySkinViewModel.m
// FUBeautyComponent
//
// Created by on 2022/7/27.
//
#import "FUBeautySkinViewModel.h"
#import "FUBeautyDefine.h"
#import <FURenderKit/FURenderKit.h>
@interface FUBeautySkinViewModel ()
@property (nonatomic, copy) NSArray<FUBeautySkinModel *> *beautySkins;
@end
@implementation FUBeautySkinViewModel
#pragma mark - Initializer
- (instancetype)init {
self = [super init];
if (self) {
if ([[NSUserDefaults standardUserDefaults] objectForKey:FUPersistentBeautySkinKey]) {
//
self.beautySkins = [self localSkins];
} else {
//
self.beautySkins = [self defaultSkins];
}
self.selectedIndex = -1;
}
return self;
}
#pragma mark - Instance methods
- (void)saveSkinsPersistently {
if (self.beautySkins.count == 0) {
return;
}
NSMutableArray *skins = [[NSMutableArray alloc] init];
for (FUBeautySkinModel *model in self.beautySkins) {
NSDictionary *dictionary = [model dictionaryWithValuesForKeys:@[@"name", @"type", @"currentValue", @"defaultValue", @"defaultValueInMiddle", @"ratio"]];
[skins addObject:dictionary];
}
[[NSUserDefaults standardUserDefaults] setObject:skins forKey:FUPersistentBeautySkinKey];
[[NSUserDefaults standardUserDefaults] synchronize];
}
- (void)setSkinValue:(double)value {
if (self.selectedIndex < 0 || self.selectedIndex >= self.beautySkins.count) {
return;
}
FUBeautySkinModel *model = self.beautySkins[self.selectedIndex];
model.currentValue = value * model.ratio;
[self setValue:model.currentValue forType:model.type];
}
- (void)setAllSkinValues {
for (FUBeautySkinModel *skin in self.beautySkins) {
[self setValue:skin.currentValue forType:skin.type];
}
}
- (void)recoverAllSkinValuesToDefault {
for (FUBeautySkinModel *skin in self.beautySkins) {
skin.currentValue = skin.defaultValue;
[self setValue:skin.currentValue forType:skin.type];
}
}
#pragma mark - Private methods
- (void)setValue:(double)value forType:(FUBeautySkin)type {
switch (type) {
case FUBeautySkinBlurLevel:
[FURenderKit shareRenderKit].beauty.blurLevel = value;
break;
case FUBeautySkinColorLevel:
[FURenderKit shareRenderKit].beauty.colorLevel = value;
break;
case FUBeautySkinRedLevel:
[FURenderKit shareRenderKit].beauty.redLevel = value;
break;
case FUBeautySkinSharpen:
[FURenderKit shareRenderKit].beauty.sharpen = value;
break;
case FUBeautySkinFaceThreed:
[FURenderKit shareRenderKit].beauty.faceThreed = value;
break;
case FUBeautySkinEyeBright:
[FURenderKit shareRenderKit].beauty.eyeBright = value;
break;
case FUBeautySkinToothWhiten:
[FURenderKit shareRenderKit].beauty.toothWhiten = value;
break;
case FUBeautySkinRemovePouchStrength:
[FURenderKit shareRenderKit].beauty.removePouchStrength = value;
break;
case FUBeautySkinRemoveNasolabialFoldsStrength:
[FURenderKit shareRenderKit].beauty.removeNasolabialFoldsStrength = value;
break;
}
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[self performSelector:@selector(saveSkinsPersistently) withObject:nil afterDelay:2];
}
- (NSArray<FUBeautySkinModel *> *)localSkins {
NSArray *skins = [[NSUserDefaults standardUserDefaults] objectForKey:FUPersistentBeautySkinKey];
NSMutableArray *mutableSkins = [[NSMutableArray alloc] init];
for (NSDictionary *skin in skins) {
FUBeautySkinModel *model = [[FUBeautySkinModel alloc] init];
[model setValuesForKeysWithDictionary:skin];
[mutableSkins addObject:model];
}
return [mutableSkins copy];
}
- (NSArray<FUBeautySkinModel *> *)defaultSkins {
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSString *skinPath = [bundle pathForResource:@"beauty_skin" ofType:@"json"];
NSArray<NSDictionary *> *skinData = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfFile:skinPath] options:NSJSONReadingMutableContainers error:nil];
NSMutableArray *skins = [[NSMutableArray alloc] init];
for (NSDictionary *dictionary in skinData) {
FUBeautySkinModel *model = [[FUBeautySkinModel alloc] init];
[model setValuesForKeysWithDictionary:dictionary];
[skins addObject:model];
}
return [skins copy];
}
#pragma mark - Getters
- (BOOL)isDefaultValue {
for (FUBeautySkinModel *skin in self.beautySkins) {
int currentIntValue = skin.defaultValueInMiddle ? (int)(skin.currentValue / skin.ratio * 100 - 50) : (int)(skin.currentValue / skin.ratio * 100);
int defaultIntValue = skin.defaultValueInMiddle ? (int)(skin.defaultValue / skin.ratio * 100 - 50) : (int)(skin.defaultValue / skin.ratio * 100);
if (currentIntValue != defaultIntValue) {
return NO;
}
}
return YES;
}
@end

Binary file not shown.

View File

@@ -0,0 +1,15 @@
Pod::Spec.new do |s|
s.name = "FUCommonUIComponent"
s.version = "1.0"
s.license = 'MIT'
s.summary = "UI components"
s.description = "Some UI components of FULiveDemo."
s.homepage = "https://github.com/Faceunity/FULiveDemo"
s.author = { 'faceunity' => 'dev@faceunity.com' }
s.platform = :ios, "9.0"
s.source = { "http": "https://github.com/Faceunity/FULiveDemo"}
s.source_files = '**/*.{h,m}'
# s.resources = 'FUCommonUIComponent/Resource/**/*.{png,xcassets}'
s.requires_arc = true
end

View File

@@ -0,0 +1,482 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 55;
objects = {
/* Begin PBXBuildFile section */
E3406A45288AAC5000373612 /* FUSquareButton.m in Sources */ = {isa = PBXBuildFile; fileRef = E3406A43288AAC5000373612 /* FUSquareButton.m */; };
E3406A46288AAC5000373612 /* FUSquareButton.h in Headers */ = {isa = PBXBuildFile; fileRef = E3406A44288AAC5000373612 /* FUSquareButton.h */; settings = {ATTRIBUTES = (Public, ); }; };
E34CFFA02892873900712C63 /* FUAlertController.m in Sources */ = {isa = PBXBuildFile; fileRef = E34CFF9C2892873800712C63 /* FUAlertController.m */; };
E34CFFA12892873900712C63 /* FUAlertManager.h in Headers */ = {isa = PBXBuildFile; fileRef = E34CFF9D2892873900712C63 /* FUAlertManager.h */; settings = {ATTRIBUTES = (Public, ); }; };
E34CFFA22892873900712C63 /* FUAlertManager.m in Sources */ = {isa = PBXBuildFile; fileRef = E34CFF9E2892873900712C63 /* FUAlertManager.m */; };
E34CFFA32892873900712C63 /* FUAlertController.h in Headers */ = {isa = PBXBuildFile; fileRef = E34CFF9F2892873900712C63 /* FUAlertController.h */; };
E35333632865AEDF00FFED9D /* FUItemsView.h in Headers */ = {isa = PBXBuildFile; fileRef = E35333612865AEDF00FFED9D /* FUItemsView.h */; settings = {ATTRIBUTES = (Public, ); }; };
E35333642865AEDF00FFED9D /* FUItemsView.m in Sources */ = {isa = PBXBuildFile; fileRef = E35333622865AEDF00FFED9D /* FUItemsView.m */; };
E35333672865AF7600FFED9D /* FUItemCell.h in Headers */ = {isa = PBXBuildFile; fileRef = E35333652865AF7600FFED9D /* FUItemCell.h */; settings = {ATTRIBUTES = (Public, ); }; };
E35333682865AF7600FFED9D /* FUItemCell.m in Sources */ = {isa = PBXBuildFile; fileRef = E35333662865AF7600FFED9D /* FUItemCell.m */; };
E36A0825285C895900A83553 /* FUCommonUIComponent.h in Headers */ = {isa = PBXBuildFile; fileRef = E36A0824285C895900A83553 /* FUCommonUIComponent.h */; settings = {ATTRIBUTES = (Public, ); }; };
E36A082D285C89C200A83553 /* FUSegmentBar.m in Sources */ = {isa = PBXBuildFile; fileRef = E36A082B285C89C200A83553 /* FUSegmentBar.m */; };
E36A082E285C89C200A83553 /* FUSegmentBar.h in Headers */ = {isa = PBXBuildFile; fileRef = E36A082C285C89C200A83553 /* FUSegmentBar.h */; settings = {ATTRIBUTES = (Public, ); }; };
E3FF96232860591F004B13D7 /* FUSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = E3FF96212860591F004B13D7 /* FUSlider.m */; };
E3FF96242860591F004B13D7 /* FUSlider.h in Headers */ = {isa = PBXBuildFile; fileRef = E3FF96222860591F004B13D7 /* FUSlider.h */; settings = {ATTRIBUTES = (Public, ); }; };
E3FF962728605987004B13D7 /* FUCommonUIComponent.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E3FF962628605987004B13D7 /* FUCommonUIComponent.xcassets */; };
E3FF962E28605CA5004B13D7 /* FUInsetsLabel.h in Headers */ = {isa = PBXBuildFile; fileRef = E3FF962C28605CA5004B13D7 /* FUInsetsLabel.h */; settings = {ATTRIBUTES = (Public, ); }; };
E3FF962F28605CA5004B13D7 /* FUInsetsLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = E3FF962D28605CA5004B13D7 /* FUInsetsLabel.m */; };
E3FF963328605F66004B13D7 /* FUTipHUD.m in Sources */ = {isa = PBXBuildFile; fileRef = E3FF963128605F66004B13D7 /* FUTipHUD.m */; };
E3FF963428605F66004B13D7 /* FUTipHUD.h in Headers */ = {isa = PBXBuildFile; fileRef = E3FF963228605F66004B13D7 /* FUTipHUD.h */; settings = {ATTRIBUTES = (Public, ); }; };
E3FF966628606435004B13D7 /* FUCommonUIDefine.h in Headers */ = {isa = PBXBuildFile; fileRef = E3FF9628286059F4004B13D7 /* FUCommonUIDefine.h */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
E3406A43288AAC5000373612 /* FUSquareButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FUSquareButton.m; sourceTree = "<group>"; };
E3406A44288AAC5000373612 /* FUSquareButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FUSquareButton.h; sourceTree = "<group>"; };
E34CFF9C2892873800712C63 /* FUAlertController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FUAlertController.m; sourceTree = "<group>"; };
E34CFF9D2892873900712C63 /* FUAlertManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FUAlertManager.h; sourceTree = "<group>"; };
E34CFF9E2892873900712C63 /* FUAlertManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FUAlertManager.m; sourceTree = "<group>"; };
E34CFF9F2892873900712C63 /* FUAlertController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FUAlertController.h; sourceTree = "<group>"; };
E35333612865AEDF00FFED9D /* FUItemsView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FUItemsView.h; sourceTree = "<group>"; };
E35333622865AEDF00FFED9D /* FUItemsView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FUItemsView.m; sourceTree = "<group>"; };
E35333652865AF7600FFED9D /* FUItemCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FUItemCell.h; sourceTree = "<group>"; };
E35333662865AF7600FFED9D /* FUItemCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FUItemCell.m; sourceTree = "<group>"; };
E36A0821285C895900A83553 /* FUCommonUIComponent.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FUCommonUIComponent.framework; sourceTree = BUILT_PRODUCTS_DIR; };
E36A0824285C895900A83553 /* FUCommonUIComponent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FUCommonUIComponent.h; sourceTree = "<group>"; };
E36A082B285C89C200A83553 /* FUSegmentBar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FUSegmentBar.m; sourceTree = "<group>"; };
E36A082C285C89C200A83553 /* FUSegmentBar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FUSegmentBar.h; sourceTree = "<group>"; };
E3FF96212860591F004B13D7 /* FUSlider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FUSlider.m; sourceTree = "<group>"; };
E3FF96222860591F004B13D7 /* FUSlider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FUSlider.h; sourceTree = "<group>"; };
E3FF962628605987004B13D7 /* FUCommonUIComponent.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = FUCommonUIComponent.xcassets; sourceTree = "<group>"; };
E3FF9628286059F4004B13D7 /* FUCommonUIDefine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FUCommonUIDefine.h; sourceTree = "<group>"; };
E3FF962C28605CA5004B13D7 /* FUInsetsLabel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FUInsetsLabel.h; sourceTree = "<group>"; };
E3FF962D28605CA5004B13D7 /* FUInsetsLabel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FUInsetsLabel.m; sourceTree = "<group>"; };
E3FF963128605F66004B13D7 /* FUTipHUD.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FUTipHUD.m; sourceTree = "<group>"; };
E3FF963228605F66004B13D7 /* FUTipHUD.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FUTipHUD.h; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
E36A081E285C895900A83553 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
E3406A42288AAC3F00373612 /* FUSquareButton */ = {
isa = PBXGroup;
children = (
E3406A44288AAC5000373612 /* FUSquareButton.h */,
E3406A43288AAC5000373612 /* FUSquareButton.m */,
);
path = FUSquareButton;
sourceTree = "<group>";
};
E34CFF9B2892871E00712C63 /* FUAlert */ = {
isa = PBXGroup;
children = (
E34CFF9F2892873900712C63 /* FUAlertController.h */,
E34CFF9C2892873800712C63 /* FUAlertController.m */,
E34CFF9D2892873900712C63 /* FUAlertManager.h */,
E34CFF9E2892873900712C63 /* FUAlertManager.m */,
);
path = FUAlert;
sourceTree = "<group>";
};
E35333602865AD1200FFED9D /* FUItemsView */ = {
isa = PBXGroup;
children = (
E35333612865AEDF00FFED9D /* FUItemsView.h */,
E35333622865AEDF00FFED9D /* FUItemsView.m */,
E35333652865AF7600FFED9D /* FUItemCell.h */,
E35333662865AF7600FFED9D /* FUItemCell.m */,
);
path = FUItemsView;
sourceTree = "<group>";
};
E36A0817285C895900A83553 = {
isa = PBXGroup;
children = (
E36A0823285C895900A83553 /* FUCommonUIComponent */,
E36A0822285C895900A83553 /* Products */,
);
sourceTree = "<group>";
};
E36A0822285C895900A83553 /* Products */ = {
isa = PBXGroup;
children = (
E36A0821285C895900A83553 /* FUCommonUIComponent.framework */,
);
name = Products;
sourceTree = "<group>";
};
E36A0823285C895900A83553 /* FUCommonUIComponent */ = {
isa = PBXGroup;
children = (
E34CFF9B2892871E00712C63 /* FUAlert */,
E3406A42288AAC3F00373612 /* FUSquareButton */,
E35333602865AD1200FFED9D /* FUItemsView */,
E3FF963028605F24004B13D7 /* FUTipHUD */,
E3FF962B28605C79004B13D7 /* FUInsetsLabel */,
E3FF962A28605B8C004B13D7 /* FUSegmentBar */,
E3FF962928605B86004B13D7 /* FUSlider */,
E3FF96252860596A004B13D7 /* Resource */,
E36A0824285C895900A83553 /* FUCommonUIComponent.h */,
E3FF9628286059F4004B13D7 /* FUCommonUIDefine.h */,
);
path = FUCommonUIComponent;
sourceTree = "<group>";
};
E3FF96252860596A004B13D7 /* Resource */ = {
isa = PBXGroup;
children = (
E3FF962628605987004B13D7 /* FUCommonUIComponent.xcassets */,
);
path = Resource;
sourceTree = "<group>";
};
E3FF962928605B86004B13D7 /* FUSlider */ = {
isa = PBXGroup;
children = (
E3FF96222860591F004B13D7 /* FUSlider.h */,
E3FF96212860591F004B13D7 /* FUSlider.m */,
);
path = FUSlider;
sourceTree = "<group>";
};
E3FF962A28605B8C004B13D7 /* FUSegmentBar */ = {
isa = PBXGroup;
children = (
E36A082C285C89C200A83553 /* FUSegmentBar.h */,
E36A082B285C89C200A83553 /* FUSegmentBar.m */,
);
path = FUSegmentBar;
sourceTree = "<group>";
};
E3FF962B28605C79004B13D7 /* FUInsetsLabel */ = {
isa = PBXGroup;
children = (
E3FF962C28605CA5004B13D7 /* FUInsetsLabel.h */,
E3FF962D28605CA5004B13D7 /* FUInsetsLabel.m */,
);
path = FUInsetsLabel;
sourceTree = "<group>";
};
E3FF963028605F24004B13D7 /* FUTipHUD */ = {
isa = PBXGroup;
children = (
E3FF963228605F66004B13D7 /* FUTipHUD.h */,
E3FF963128605F66004B13D7 /* FUTipHUD.m */,
);
path = FUTipHUD;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
E36A081C285C895900A83553 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
E3FF96242860591F004B13D7 /* FUSlider.h in Headers */,
E34CFFA32892873900712C63 /* FUAlertController.h in Headers */,
E36A0825285C895900A83553 /* FUCommonUIComponent.h in Headers */,
E3FF962E28605CA5004B13D7 /* FUInsetsLabel.h in Headers */,
E35333632865AEDF00FFED9D /* FUItemsView.h in Headers */,
E3406A46288AAC5000373612 /* FUSquareButton.h in Headers */,
E35333672865AF7600FFED9D /* FUItemCell.h in Headers */,
E3FF966628606435004B13D7 /* FUCommonUIDefine.h in Headers */,
E34CFFA12892873900712C63 /* FUAlertManager.h in Headers */,
E36A082E285C89C200A83553 /* FUSegmentBar.h in Headers */,
E3FF963428605F66004B13D7 /* FUTipHUD.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
E36A0820285C895900A83553 /* FUCommonUIComponent */ = {
isa = PBXNativeTarget;
buildConfigurationList = E36A0828285C895900A83553 /* Build configuration list for PBXNativeTarget "FUCommonUIComponent" */;
buildPhases = (
E36A081C285C895900A83553 /* Headers */,
E36A081D285C895900A83553 /* Sources */,
E36A081E285C895900A83553 /* Frameworks */,
E36A081F285C895900A83553 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = FUCommonUIComponent;
productName = FUCommonUIComponent;
productReference = E36A0821285C895900A83553 /* FUCommonUIComponent.framework */;
productType = "com.apple.product-type.framework";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
E36A0818285C895900A83553 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastUpgradeCheck = 1330;
TargetAttributes = {
E36A0820285C895900A83553 = {
CreatedOnToolsVersion = 13.3.1;
};
};
};
buildConfigurationList = E36A081B285C895900A83553 /* Build configuration list for PBXProject "FUCommonUIComponent" */;
compatibilityVersion = "Xcode 13.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = E36A0817285C895900A83553;
productRefGroup = E36A0822285C895900A83553 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
E36A0820285C895900A83553 /* FUCommonUIComponent */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
E36A081F285C895900A83553 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E3FF962728605987004B13D7 /* FUCommonUIComponent.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
E36A081D285C895900A83553 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E35333642865AEDF00FFED9D /* FUItemsView.m in Sources */,
E36A082D285C89C200A83553 /* FUSegmentBar.m in Sources */,
E34CFFA22892873900712C63 /* FUAlertManager.m in Sources */,
E3FF96232860591F004B13D7 /* FUSlider.m in Sources */,
E35333682865AF7600FFED9D /* FUItemCell.m in Sources */,
E34CFFA02892873900712C63 /* FUAlertController.m in Sources */,
E3FF963328605F66004B13D7 /* FUTipHUD.m in Sources */,
E3FF962F28605CA5004B13D7 /* FUInsetsLabel.m in Sources */,
E3406A45288AAC5000373612 /* FUSquareButton.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
E36A0826285C895900A83553 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.4;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
E36A0827285C895900A83553 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.4;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
E36A0829285C895900A83553 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 35A6KD62ZJ;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_BITCODE = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = faceunity.FUCommonUIComponent;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
E36A082A285C895900A83553 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 35A6KD62ZJ;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_BITCODE = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = faceunity.FUCommonUIComponent;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
E36A081B285C895900A83553 /* Build configuration list for PBXProject "FUCommonUIComponent" */ = {
isa = XCConfigurationList;
buildConfigurations = (
E36A0826285C895900A83553 /* Debug */,
E36A0827285C895900A83553 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
E36A0828285C895900A83553 /* Build configuration list for PBXNativeTarget "FUCommonUIComponent" */ = {
isa = XCConfigurationList;
buildConfigurations = (
E36A0829285C895900A83553 /* Debug */,
E36A082A285C895900A83553 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = E36A0818285C895900A83553 /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

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

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1330"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "E36A0820285C895900A83553"
BuildableName = "FUCommonUIComponent.framework"
BlueprintName = "FUCommonUIComponent"
ReferencedContainer = "container:FUCommonUIComponent.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "E36A0820285C895900A83553"
BuildableName = "FUCommonUIComponent.framework"
BlueprintName = "FUCommonUIComponent"
ReferencedContainer = "container:FUCommonUIComponent.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,54 @@
//
// FUAlertController.h
//
// Created by 项林平 on 2019/6/21.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@class FUAlertController;
@interface FUAlertModel : NSObject
@property (nonatomic, copy) NSString *alertTitle;
@property (nonatomic, copy) NSString *alertMessage;
@property (nonatomic, assign) UIAlertControllerStyle preferredStyle;
- (instancetype) initWithTitle:(NSString *)title message:(NSString *)message style:(UIAlertControllerStyle)style;
@end
typedef void (^FUAlert)(FUAlertController *controller);
typedef FUAlertController * _Nonnull (^FUShowAlert)(UIViewController *controller);
typedef FUAlertController * _Nonnull (^FUActions)(NSArray<UIAlertAction *> *actions);
typedef FUAlertController * _Nullable (^FUSourceView)(UIView *sourceView);
@interface FUAlertController : UIAlertController
+ (FUAlertController *)makeAlert:(FUAlert)block alertModel:(FUAlertModel *)model;
/**
设置Actions
@return Self
*/
- (FUActions)actionItems;
/**
当设备为iPad时设置SourceView
@return Self
*/
- (FUSourceView)sourceView;
/**
显示Alert
@return Self
*/
- (FUShowAlert)showAlert;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,57 @@
//
// FUAlertController.m
//
// Created by on 2019/6/21.
//
#import "FUAlertController.h"
@implementation FUAlertModel
- (instancetype)initWithTitle:(NSString *)title message:(NSString *)message style:(UIAlertControllerStyle)style {
self = [super init];
if (self) {
self.alertTitle = title;
self.alertMessage = message;
self.preferredStyle = style;
}
return self;
}
@end
@implementation FUAlertController
+(FUAlertController *)makeAlert:(FUAlert)block alertModel:(FUAlertModel *)model {
FUAlertController *alert = [FUAlertController alertControllerWithTitle:model.alertTitle message:model.alertMessage preferredStyle:model.preferredStyle];
block(alert);
return alert;
}
- (FUActions)actionItems {
FUActions actionsBlock = ^(NSArray<UIAlertAction *> *actions) {
[actions enumerateObjectsUsingBlock:^(UIAlertAction * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[self addAction:obj];
}];
return self;
};
return actionsBlock;
}
- (FUShowAlert)showAlert {
FUShowAlert showBlock = ^(UIViewController *controller) {
[controller presentViewController:self animated:YES completion:nil];
return self;
};
return showBlock;
}
- (FUSourceView)sourceView {
FUSourceView sourceViewBlock = ^(UIView *sourceView) {
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad && sourceView) {
self.popoverPresentationController.sourceView = sourceView;
self.popoverPresentationController.sourceRect = sourceView.bounds;
self.popoverPresentationController.permittedArrowDirections = UIPopoverArrowDirectionAny;
}
return self;
};
return sourceViewBlock;
}
@end

View File

@@ -0,0 +1,48 @@
//
// FUAlertManager.h
//
// Created by 项林平 on 2019/9/25.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface FUAlertManager : NSObject
/// Alert
/// @param titleString title
/// @param messageString message
/// @param cancelString 取消
/// @param confirmString 确定
/// @param viewController 控制器
/// @param confirm 确定闭包
/// @param cancel 取消闭包
+ (void)showAlertWithTitle:(nullable NSString *)titleString
message:(nullable NSString *)messageString
cancel:(nullable NSString *)cancelString
confirm:(nullable NSString *)confirmString
inController:(nullable UIViewController *)viewController
confirmHandler:(nullable void (^)(void))confirm
cancelHandler:(nullable void (^)(void))cancel;
/// ActionSheet
/// @param titleString title
/// @param messageString message
/// @param cancelString 取消
/// @param viewController 控制器
/// @param sourceView 设备为iPad时需要传入
/// @param actions 选项
/// @param actionHandler 选项闭包
+ (void)showActionSheetWithTitle:(nullable NSString *)titleString
message:(nullable NSString *)messageString
cancel:(nullable NSString *)cancelString
inController:(nullable UIViewController *)viewController
sourceView:(nullable UIView *)sourceView
actions:(NSArray<NSString *> *)actions
actionHandler:(nullable void (^)(NSInteger index))actionHandler;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,114 @@
//
// FUAlertManager.m
//
// Created by on 2019/9/25.
//
#import "FUAlertManager.h"
#import "FUAlertController.h"
@implementation FUAlertManager
+ (void)showAlertWithTitle:(NSString *)titleString
message:(NSString *)messageString
cancel:(NSString *)cancelString
confirm:(NSString *)confirmString
inController:(UIViewController *)viewController
confirmHandler:(void (^)(void))confirm
cancelHandler:(void (^)(void))cancel {
if (!cancelString && !confirmString) {
return;
}
FUAlertModel *alertModel = [[FUAlertModel alloc] initWithTitle:titleString message:messageString style:UIAlertControllerStyleAlert];
__block UIViewController *currentViewController = viewController;
[FUAlertController makeAlert:^(FUAlertController * _Nonnull controller) {
NSMutableArray *items = [[NSMutableArray alloc] init];
if (cancelString) {
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:cancelString style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
if (cancel) {
cancel();
}
}];
//Action
[cancelAction setValue:[UIColor colorWithRed:44/255.0 green:46/255.0 blue:48/255.0 alpha:1.0] forKey:@"titleTextColor"];
[items addObject:cancelAction];
}
if (confirmString) {
UIAlertAction *confirmAction = [UIAlertAction actionWithTitle:confirmString style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action) {
if (confirm) {
confirm();
}
}];
[confirmAction setValue:[UIColor colorWithRed:103/255.f green:195/255.f blue:103/255.f alpha:1] forKey:@"titleTextColor"];
[items addObject:confirmAction];
}
if (!currentViewController) {
currentViewController = [self topViewController];
}
if (!currentViewController) {
NSLog(@"FUAlert: viewController can not be nil!");
return;
}
controller.actionItems(items).showAlert(currentViewController);
} alertModel:alertModel];
}
+ (void)showActionSheetWithTitle:(NSString *)titleString
message:(NSString *)messageString
cancel:(NSString *)cancelString
inController:(UIViewController *)viewController
sourceView:(UIView *)sourceView
actions:(NSArray<NSString *> *)actions
actionHandler:(void (^)(NSInteger))actionHandler {
if (actions.count == 0) {
return;
}
FUAlertModel *alertModel = [[FUAlertModel alloc] initWithTitle:titleString message:messageString style:UIAlertControllerStyleActionSheet];
__block UIViewController *currentViewController = viewController;
[FUAlertController makeAlert:^(FUAlertController * _Nonnull controller) {
NSMutableArray *items = [[NSMutableArray alloc] init];
if (cancelString) {
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:cancelString style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
}];
[items addObject:cancelAction];
}
[actions enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
UIAlertAction *alertAction = [UIAlertAction actionWithTitle:obj style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
if (actionHandler) {
actionHandler(idx);
}
}];
[items addObject:alertAction];
}];
if (!currentViewController) {
currentViewController = [self topViewController];
}
if (!currentViewController) {
NSLog(@"FUAlert: viewController can not be nil!");
return;
}
controller.actionItems(items).sourceView(sourceView).showAlert(currentViewController);
} alertModel:alertModel];
}
+ (UIViewController *)topViewController {
UIViewController *root = [UIApplication sharedApplication].delegate.window.rootViewController;
return [self currentViewControllerWithRootViewController:root];
}
+ (UIViewController *)currentViewControllerWithRootViewController:(UIViewController *)viewController {
if (viewController.presentedViewController) {
return [self currentViewControllerWithRootViewController:viewController.presentedViewController];
} else if ([viewController isKindOfClass:[UINavigationController class]]) {
UINavigationController *navigation = (UINavigationController *)viewController;
return [self currentViewControllerWithRootViewController:navigation.visibleViewController];
} else if ([viewController isKindOfClass:[UITabBarController class]]) {
UITabBarController *tabBar = (UITabBarController *)viewController;
return [self currentViewControllerWithRootViewController:tabBar.selectedViewController];
} else {
return viewController;
}
}
@end

View File

@@ -0,0 +1,25 @@
//
// FUCommonUIComponent.h
// FUCommonUIComponent
//
// Created by 项林平 on 2022/6/17.
//
#import <Foundation/Foundation.h>
#import "FUSegmentBar.h"
#import "FUTipHUD.h"
#import "FUInsetsLabel.h"
#import "FUSlider.h"
#import "FUItemsView.h"
#import "FUItemCell.h"
#import "FUSquareButton.h"
#import "FUAlertManager.h"
//! Project version number for FUCommonUIComponent.
FOUNDATION_EXPORT double FUCommonUIComponentVersionNumber;
//! Project version string for FUCommonUIComponent.
FOUNDATION_EXPORT const unsigned char FUCommonUIComponentVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <FUCommonUIComponent/PublicHeader.h>

View File

@@ -0,0 +1,20 @@
//
// FUCommonUIDefine.h
// FUCommonUIComponent
//
// Created by 项林平 on 2022/6/20.
//
#import <AVFoundation/AVFoundation.h>
#import <UIKit/UIKit.h>
#ifndef FUCommonUIDefine_h
#define FUCommonUIDefine_h
static inline UIImage * FUCommonUIImageNamed(NSString *name) {
NSString *path = [[NSBundle mainBundle] pathForResource:@"FUCommonUIComponent" ofType:@"framework" inDirectory:@"Frameworks"];
NSBundle *bundle = [NSBundle bundleWithPath:path];
return [UIImage imageNamed:name inBundle:bundle compatibleWithTraitCollection:nil];;
}
#endif /* FUCommonUIDefine_h */

View File

@@ -0,0 +1,18 @@
//
// FUInsetsLabel.h
// FUCommonUIComponent
//
// Created by 项林平 on 2022/6/20.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface FUInsetsLabel : UILabel
- (instancetype)initWithFrame:(CGRect)frame insets:(UIEdgeInsets)insets;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,48 @@
//
// FUInsetsLabel.m
// FUCommonUIComponent
//
// Created by on 2022/6/20.
//
#import "FUInsetsLabel.h"
@interface FUInsetsLabel ()
@property (nonatomic) UIEdgeInsets insets;
@end
@implementation FUInsetsLabel
- (instancetype)initWithFrame:(CGRect)frame {
return [self initWithFrame:frame insets:UIEdgeInsetsMake(8, 8, 8, 8)];
}
- (instancetype)initWithFrame:(CGRect)frame insets:(UIEdgeInsets)insets {
self = [super initWithFrame:frame];
if (self) {
self.insets = insets;
}
return self;
}
- (void)drawTextInRect:(CGRect)rect {
[super drawTextInRect:UIEdgeInsetsInsetRect(rect, self.insets)];
}
- (CGSize)sizeThatFits:(CGSize)size {
CGSize fitSize = [super sizeThatFits:size];
fitSize.width += self.insets.left + self.insets.right;
fitSize.height += self.insets.top + self.insets.bottom;
return fitSize;
}
- (CGSize)intrinsicContentSize {
CGSize contentSize = [super intrinsicContentSize];
contentSize.width += self.insets.left + self.insets.right;
contentSize.height += self.insets.top + self.insets.bottom;
return contentSize;
}
@end

View File

@@ -0,0 +1,20 @@
//
// FUItemCell.h
// FUCommonUIComponent
//
// Created by 项林平 on 2022/6/24.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface FUItemCell : UICollectionViewCell
@property (nonatomic, strong, readonly) UIImageView *imageView;
@property (nonatomic, strong, readonly) UIActivityIndicatorView *indicatorView;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,68 @@
//
// FUItemCell.m
// FUCommonUIComponent
//
// Created by on 2022/6/24.
//
#import "FUItemCell.h"
@interface FUItemCell ()
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) UIActivityIndicatorView *indicatorView;
@end
@implementation FUItemCell
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self.contentView addSubview:self.imageView];
NSLayoutConstraint *imageLeading = [NSLayoutConstraint constraintWithItem:self.imageView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0];
NSLayoutConstraint *imageTrailing = [NSLayoutConstraint constraintWithItem:self.imageView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:0];
NSLayoutConstraint *imageTop = [NSLayoutConstraint constraintWithItem:self.imageView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];
NSLayoutConstraint *imageBottom = [NSLayoutConstraint constraintWithItem:self.imageView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0];
[self.contentView addConstraints:@[imageLeading, imageTrailing, imageTop, imageBottom]];
[self.contentView addSubview:self.indicatorView];
NSLayoutConstraint *centerYConstraint = [NSLayoutConstraint constraintWithItem:self.indicatorView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
NSLayoutConstraint *centerXConstraint = [NSLayoutConstraint constraintWithItem:self.indicatorView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeCenterX multiplier:1 constant:0];
[self.contentView addConstraints:@[centerXConstraint, centerYConstraint]];
}
return self;
}
#pragma mark - Setters
- (void)setSelected:(BOOL)selected {
[super setSelected:selected];
self.imageView.layer.borderWidth = selected ? 3.f : 0;
self.imageView.layer.borderColor = selected ? [UIColor colorWithRed:103/255.f green:195/255.f blue:103/255.f alpha:1].CGColor : [UIColor clearColor].CGColor;
}
#pragma mark - Getters
- (UIImageView *)imageView {
if (!_imageView) {
_imageView = [[UIImageView alloc] init];
_imageView.translatesAutoresizingMaskIntoConstraints = NO;
_imageView.layer.masksToBounds = YES;
_imageView.layer.cornerRadius = 30.f;
}
return _imageView;
}
- (UIActivityIndicatorView *)indicatorView {
if (!_indicatorView) {
_indicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
_indicatorView.hidden = YES;
_indicatorView.translatesAutoresizingMaskIntoConstraints = NO;
}
return _indicatorView;
}
@end

View File

@@ -0,0 +1,49 @@
//
// FUItemsView.h
// FUCommonUIComponent
//
// Created by 项林平 on 2022/6/24.
//
#import <UIKit/UIKit.h>
@class FUItemsView;
NS_ASSUME_NONNULL_BEGIN
@protocol FUItemsViewDelegate <NSObject>
@optional
- (void)itemsView:(FUItemsView *)itemsView didSelectItemAtIndex:(NSInteger)index;
@end
@interface FUItemsView : UIView
/// 数据源
/// @discussion 外部传入的icon名数组查找不到icon会展示空白
@property (nonatomic, copy) NSArray<NSString *> *items;
/// 当前选中索引
/// @discussion 默认为 -1-1为取消选中
@property (nonatomic, assign) NSInteger selectedIndex;
@property (nonatomic, weak) id<FUItemsViewDelegate> delegate;
/// 初始化
/// @param frame Frame
/// @param topSpacing 顶部预留空间默认为0
- (instancetype)initWithFrame:(CGRect)frame topSpacing:(CGFloat)topSpacing;
/// 开始当前选中项动画
/// @note 开始动画后无法选择其他项
- (void)startAnimation;
/// 结束当前选中项动画
/// @note 结束动画后可以继续选择其他项
- (void)stopAnimation;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,197 @@
//
// FUItemsView.m
// FUCommonUIComponent
//
// Created by on 2022/6/24.
//
#import "FUItemsView.h"
#import "FUItemCell.h"
static NSString * const kFUItemCellIdentifier = @"FUItemCell";
@interface FUItemsView ()<UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>
@property (nonatomic, strong) UICollectionView *collectionView;
@property (nonatomic, assign) CGFloat collectionTopConstant;
@end
@implementation FUItemsView
#pragma mark - Initializer
- (instancetype)initWithFrame:(CGRect)frame topSpacing:(CGFloat)topSpacing {
self = [super initWithFrame:frame];
if (self) {
self.collectionTopConstant = topSpacing;
[self configureUI];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self configureUI];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
[self configureUI];
}
return self;
}
#pragma mark - UI
- (void)configureUI {
self.backgroundColor = [UIColor clearColor];
UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:effect];
effectView.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:effectView];
NSLayoutConstraint *effectLeading = [NSLayoutConstraint constraintWithItem:effectView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0];
NSLayoutConstraint *effectTrailing = [NSLayoutConstraint constraintWithItem:effectView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:0];
NSLayoutConstraint *effectTop = [NSLayoutConstraint constraintWithItem:effectView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];
NSLayoutConstraint *effectBottom = [NSLayoutConstraint constraintWithItem:effectView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0];
[self addConstraints:@[effectLeading, effectTrailing, effectTop, effectBottom]];
[self addSubview:self.collectionView];
NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:self.collectionView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0];
NSLayoutConstraint *trailing = [NSLayoutConstraint constraintWithItem:self.collectionView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:0];
NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:self.collectionView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1.0 constant:self.collectionTopConstant];
NSLayoutConstraint *height = [NSLayoutConstraint constraintWithItem:self.collectionView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:84];
[self addConstraints:@[leading, trailing, top]];
[self.collectionView addConstraint:height];
_selectedIndex = -1;
}
#pragma mark - Instance methods
- (void)startAnimation {
if (_selectedIndex < 0 && _selectedIndex >= self.items.count) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
FUItemCell *selectedCell = (FUItemCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:self.selectedIndex inSection:0]];
if (selectedCell) {
[selectedCell.indicatorView startAnimating];
self.collectionView.userInteractionEnabled = NO;
}
});
}
- (void)stopAnimation {
if (_selectedIndex < 0 && _selectedIndex >= self.items.count) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
FUItemCell *selectedCell = (FUItemCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:self.selectedIndex inSection:0]];
if (selectedCell) {
[selectedCell.indicatorView stopAnimating];
self.collectionView.userInteractionEnabled = YES;
}
});
}
#pragma mark - Private methods
- (UIImage *)imageWithImageName:(NSString *)imageName {
UIImage *resultImage = [UIImage imageNamed:imageName];
if (!resultImage) {
NSString *path = [[NSBundle mainBundle] pathForResource:imageName ofType:@"png"];
resultImage = [UIImage imageWithContentsOfFile:path];
}
if (!resultImage) {
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.png", imageName]];
resultImage = [UIImage imageWithContentsOfFile:path];
}
return resultImage;
}
#pragma mark - Collection view data source
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.items.count;
}
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
FUItemCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kFUItemCellIdentifier forIndexPath:indexPath];
NSString *item = self.items[indexPath.item];
cell.imageView.image = [self imageWithImageName:item];
return cell;
}
#pragma mark - Collection view delegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.item == _selectedIndex) {
return;
}
_selectedIndex = indexPath.item;
if (self.delegate && [self.delegate respondsToSelector:@selector(itemsView:didSelectItemAtIndex:)]) {
[self.delegate itemsView:self didSelectItemAtIndex:indexPath.item];
}
}
#pragma mark - Collection view delegate flow layout
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return CGSizeMake(60, 60);
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section{
return UIEdgeInsetsMake(12, 16, 12, 16);
}
#pragma mark - Setters
- (void)setItems:(NSArray<NSString *> *)items {
_items = items;
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
});
}
- (void)setSelectedIndex:(NSInteger)selectedIndex {
if (selectedIndex < 0 || selectedIndex >= self.items.count) {
return;
}
_selectedIndex = selectedIndex;
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView selectItemAtIndexPath:[NSIndexPath indexPathForItem:selectedIndex inSection:0] animated:NO scrollPosition:UICollectionViewScrollPositionNone];
if (self.delegate && [self.delegate respondsToSelector:@selector(itemsView:didSelectItemAtIndex:)]) {
[self.delegate itemsView:self didSelectItemAtIndex:selectedIndex];
}
});
}
#pragma mark - Getters
- (UICollectionView *)collectionView {
if (!_collectionView) {
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:flowLayout];
_collectionView.backgroundColor = [UIColor clearColor];
_collectionView.showsHorizontalScrollIndicator = NO;
_collectionView.translatesAutoresizingMaskIntoConstraints = NO;
_collectionView.dataSource = self;
_collectionView.delegate = self;
[_collectionView registerClass:[FUItemCell class] forCellWithReuseIdentifier:kFUItemCellIdentifier];
}
return _collectionView;
}
@end

View File

@@ -0,0 +1,77 @@
//
// FUSegmentBar.h
// FULiveDemo
//
// Created by 项林平 on 2021/9/26.
// Copyright © 2021 FaceUnity. All rights reserved.
//
#import <UIKit/UIKit.h>
@class FUSegmentBar;
NS_ASSUME_NONNULL_BEGIN
@protocol FUSegmentBarDelegate <NSObject>
- (void)segmentBar:(FUSegmentBar *)segmentBar didSelectItemAtIndex:(NSUInteger)index;
@optional
- (BOOL)segmentBar:(FUSegmentBar *)segmentBar shouldSelectItemAtIndex:(NSInteger)index;
- (BOOL)segmentBar:(FUSegmentBar *)segmentBar shouldDisableItemAtIndex:(NSInteger)index;
@end
@interface FUSegmentBarConfigurations : NSObject
/// 普通颜色
@property (nonatomic, strong) UIColor *normalTitleColor;
/// 选中状态颜色
@property (nonatomic, strong) UIColor *selectedTitleColor;
/// 无法选中状态颜色
@property (nonatomic, strong) UIColor *disabledTitleColor;
/// 字体
@property (nonatomic, strong) UIFont *titleFont;
@end
@interface FUSegmentBar : UIView
@property (nonatomic, weak) id<FUSegmentBarDelegate> delegate;
/// 当前选中项索引
/// @discussion 默认为-1-1为取消选中
@property (nonatomic, assign, readonly) NSInteger selectedIndex;
/// Unavailable initializer
+ (instancetype)new NS_UNAVAILABLE;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
/// 初始化
/// @param frame frame
/// @param titles SegmentsTitle数组
/// @param configuration 配置信息
- (instancetype)initWithFrame:(CGRect)frame titles:(NSArray<NSString *> *)titles configuration:(nullable FUSegmentBarConfigurations *)configuration;
/// 选中指定索引项
/// @param index 索引
- (void)selectItemAtIndex:(NSInteger)index;
- (void)refresh;
@end
@interface FUSegmentsBarCell : UICollectionViewCell
@property (nonatomic, strong) UILabel *segmentTitleLabel;
@property (nonatomic, strong) UIColor *segmentNormalTitleColor;
@property (nonatomic, strong) UIColor *segmentSelectedTitleColor;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,237 @@
//
// FUSegmentBar.m
// FULiveDemo
//
// Created by on 2021/9/26.
// Copyright © 2021 FaceUnity. All rights reserved.
//
#import "FUSegmentBar.h"
@interface FUSegmentBar ()
@property (nonatomic, assign) NSInteger selectedIndex;
@end
@implementation FUSegmentBarConfigurations
- (instancetype)init {
self = [super init];
if (self) {
// /
self.selectedTitleColor = [UIColor colorWithRed:103/255.0f green:195/255.0f blue:103/255.0f alpha:1.0];
self.normalTitleColor = [UIColor whiteColor];
self.disabledTitleColor = [UIColor colorWithWhite:1 alpha:0.6];
self.titleFont = [UIFont systemFontOfSize:13];
}
return self;
}
@end
static NSString * const kFUSegmentCellIdentifierKey = @"FUSegmentCellIdentifier";
@interface FUSegmentBar ()<UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>
@property (nonatomic, strong) UICollectionView *collectionView;
@property (nonatomic, strong) FUSegmentBarConfigurations *configuration;
@property (nonatomic, strong) NSMutableArray *titles;
/// cell
@property (nonatomic, copy) NSArray *itemWidths;
@end
@implementation FUSegmentBar
#pragma mark - Initializer
- (instancetype)initWithFrame:(CGRect)frame titles:(NSArray<NSString *> *)titles configuration:(FUSegmentBarConfigurations *)configuration {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor colorWithRed:5/255.0 green:15/255.0 blue:20/255.0 alpha:1.0];
self.titles = [titles mutableCopy];
self.configuration = configuration;
if (!self.configuration) {
self.configuration = [[FUSegmentBarConfigurations alloc] init];
}
//
NSMutableArray *tempWidths = [NSMutableArray arrayWithCapacity:self.titles.count];
if (self.titles.count < 7) {
//
CGFloat width = CGRectGetWidth(frame) / self.titles.count * 1.0;
for (NSInteger i = 0; i < self.titles.count; i++) {
[tempWidths addObject:@(width)];
}
} else {
//
for (NSString *title in self.titles) {
CGSize nameSize = [title sizeWithAttributes:@{NSFontAttributeName : self.configuration.titleFont}];
[tempWidths addObject:@(nameSize.width + 20)];
}
}
self.itemWidths = [tempWidths copy];
_selectedIndex = -1;
[self addSubview:self.collectionView];
}
return self;
}
#pragma mark - Instance methods
- (void)selectItemAtIndex:(NSInteger)index {
NSInteger count = self.titles.count;
if (index >= count) {
return;
}
if (self.selectedIndex == index) {
//
if (self.delegate && [self.delegate respondsToSelector:@selector(segmentBar:didSelectItemAtIndex:)]) {
[self.delegate segmentBar:self didSelectItemAtIndex:index];
}
return;
}
if (index == -1) {
//
[self.collectionView deselectItemAtIndexPath:[NSIndexPath indexPathForItem:self.selectedIndex inSection:0] animated:NO];
self.selectedIndex = -1;
} else {
//
[self.collectionView selectItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0] animated:YES scrollPosition:UICollectionViewScrollPositionCenteredHorizontally];
if (self.delegate && [self.delegate respondsToSelector:@selector(segmentBar:didSelectItemAtIndex:)]) {
[self.delegate segmentBar:self didSelectItemAtIndex:index];
}
self.selectedIndex = index;
}
}
- (void)refresh {
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
if (self.selectedIndex >= 0) {
[self.collectionView selectItemAtIndexPath:[NSIndexPath indexPathForItem:self.selectedIndex inSection:0] animated:NO scrollPosition:UICollectionViewScrollPositionNone];
}
});
}
#pragma mark - Collection view data source
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.titles.count;
}
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
FUSegmentsBarCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kFUSegmentCellIdentifierKey forIndexPath:indexPath];
cell.segmentTitleLabel.text = self.titles[indexPath.item];
cell.segmentTitleLabel.font = self.configuration.titleFont;
cell.segmentNormalTitleColor = self.configuration.normalTitleColor;
cell.segmentSelectedTitleColor = self.configuration.selectedTitleColor;
if (self.delegate && [self.delegate respondsToSelector:@selector(segmentBar:shouldDisableItemAtIndex:)]) {
if ([self.delegate segmentBar:self shouldDisableItemAtIndex:indexPath.item]) {
cell.segmentTitleLabel.textColor = self.configuration.disabledTitleColor;
}
}
return cell;
}
#pragma mark - Collection view delegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
_selectedIndex = indexPath.item;
if (self.delegate && [self.delegate respondsToSelector:@selector(segmentBar:didSelectItemAtIndex:)]) {
[self.delegate segmentBar:self didSelectItemAtIndex:indexPath.item];
}
}
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath {
if (self.delegate && [self.delegate respondsToSelector:@selector(segmentBar:shouldSelectItemAtIndex:)]) {
return [self.delegate segmentBar:self shouldSelectItemAtIndex:indexPath.item];
}
return YES;
}
#pragma mark - Collection view delegate flow layout
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return CGSizeMake([self.itemWidths[indexPath.item] floatValue], CGRectGetHeight(collectionView.frame));
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsZero;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
return 0;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
return 0;
}
#pragma mark - Getters
- (UICollectionView *)collectionView {
if (!_collectionView) {
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.frame), 49.f) collectionViewLayout:flowLayout];
_collectionView.backgroundColor = [UIColor clearColor];
_collectionView.showsHorizontalScrollIndicator = NO;
_collectionView.showsVerticalScrollIndicator = NO;
_collectionView.dataSource = self;
_collectionView.delegate = self;
[_collectionView registerClass:[FUSegmentsBarCell class] forCellWithReuseIdentifier:kFUSegmentCellIdentifierKey];
}
return _collectionView;
}
@end
@implementation FUSegmentsBarCell
#pragma mark - Initializer
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor clearColor];
[self.contentView addSubview:self.segmentTitleLabel];
NSLayoutConstraint *titleLabelCenterX = [NSLayoutConstraint constraintWithItem:self.segmentTitleLabel attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeCenterX multiplier:1 constant:0];
NSLayoutConstraint *titleLabelCenterY = [NSLayoutConstraint constraintWithItem:self.segmentTitleLabel attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
[self.contentView addConstraints:@[titleLabelCenterX, titleLabelCenterY]];
}
return self;
}
#pragma mark - Setters
- (void)setSelected:(BOOL)selected {
[super setSelected:selected];
if (selected) {
self.segmentTitleLabel.textColor = self.segmentSelectedTitleColor ? self.segmentSelectedTitleColor : [UIColor colorWithRed:103/255.0f green:195/255.0f blue:103/255.0f alpha:1.0];
} else {
self.segmentTitleLabel.textColor = self.segmentNormalTitleColor ? self.segmentNormalTitleColor : [UIColor whiteColor];
}
}
#pragma mark - Getters
- (UILabel *)segmentTitleLabel {
if (!_segmentTitleLabel) {
_segmentTitleLabel = [[UILabel alloc] initWithFrame:self.contentView.bounds];
_segmentTitleLabel.textColor = [UIColor whiteColor];
_segmentTitleLabel.font = [UIFont systemFontOfSize:13];
_segmentTitleLabel.textAlignment = NSTextAlignmentCenter;
_segmentTitleLabel.translatesAutoresizingMaskIntoConstraints = NO;
}
return _segmentTitleLabel;
}
@end

View File

@@ -0,0 +1,16 @@
//
// FUSlider.h
// FUAPIDemoBar
//
// Created by L on 2018/6/27.
// Copyright © 2018年 L. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface FUSlider : UISlider
/// 零点是否在中间默认为NO
@property (nonatomic, assign, getter=isBidirection) BOOL bidirection;
@end

View File

@@ -0,0 +1,147 @@
//
// FUSlider.m
// FUAPIDemoBar
//
// Created by L on 2018/6/27.
// Copyright © 2018 L. All rights reserved.
//
#import "FUSlider.h"
#import "FUCommonUIDefine.h"
@interface FUSlider ()
///
@property (nonatomic, strong) UILabel *tipLabel;
///
@property (nonatomic, strong) UIImageView *tipBackgroundImageView;
///
@property (nonatomic, strong) UIView *trackView;
/// 线
@property (nonatomic, strong) UIView *middleLine;
@end
@implementation FUSlider
- (void)awakeFromNib {
[super awakeFromNib];
[self configureUI];
}
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self configureUI];
}
return self;
}
- (UIView *)sliderTrackBackView {
return self.subviews.firstObject;
}
- (void)configureUI {
[self setThumbImage:FUCommonUIImageNamed(@"slider_dot") forState:UIControlStateNormal];
[self setMaximumTrackTintColor:[UIColor whiteColor]];
[self setMinimumTrackTintColor:[UIColor colorWithRed:103/255.0 green:195/255.0 blue:103/255.0 alpha:1]];
[self addSubview:self.tipBackgroundImageView];
[self addSubview:self.tipLabel];
[self.sliderTrackBackView addSubview:self.trackView];
[self addSubview:self.middleLine];
}
-(void)layoutSubviews {
[super layoutSubviews];
if (!self.trackView.hidden) {
[self.sliderTrackBackView insertSubview:self.trackView atIndex:self.subviews.count-2];
// [self bringSubviewToFront:self.trackView];
}
if (!self.middleLine.hidden) {
self.middleLine.frame = CGRectMake(CGRectGetWidth(self.bounds)/2.0 - 1, CGRectGetHeight(self.bounds)/2.0 - 4, 2, 8);
}
[self setValue:self.value animated:NO];
}
- (void)setBidirection:(BOOL)bidirection {
_bidirection = bidirection;
if (bidirection) {
self.middleLine.hidden = NO;
self.trackView.hidden = NO;
[self setMinimumTrackTintColor:[UIColor whiteColor]];
} else {
self.middleLine.hidden = YES;
self.trackView.hidden = YES;
[self setMinimumTrackTintColor:[UIColor colorWithRed:103/255.0 green:195/255.0 blue:103/255.0 alpha:1]];
}
}
- (void)setValue:(float)value animated:(BOOL)animated {
[super setValue:value animated:animated];
if (_bidirection) {
self.tipLabel.text = [NSString stringWithFormat:@"%d",(int)(value * 100 - 50)];
CGFloat currentValue = value - 0.5;
CGFloat width = currentValue * CGRectGetWidth(self.bounds);
if (width < 0 ) {
width = -width;
}
CGFloat originX = currentValue > 0 ? CGRectGetWidth(self.bounds) / 2.0 : CGRectGetWidth(self.bounds) / 2.0 - width;
self.trackView.frame = CGRectMake(originX, CGRectGetHeight(self.frame)/2.0 - 2, width, 4.0);
} else {
self.tipLabel.text = [NSString stringWithFormat:@"%d",(int)(value * 100)];
}
CGFloat x = value * (self.frame.size.width - 16) - self.tipLabel.frame.size.width * 0.5 + 8;
CGRect frame = self.tipLabel.frame;
frame.origin.x = x;
self.tipBackgroundImageView.frame = frame;
self.tipLabel.frame = frame;
self.tipLabel.hidden = !self.isTouchInside;
self.tipBackgroundImageView.hidden = !self.isTouchInside;
}
#pragma mark - Getters
- (UIImageView *)tipBackgroundImageView {
if (!_tipBackgroundImageView) {
UIImage *bgImage = FUCommonUIImageNamed(@"slider_tip_background");
_tipBackgroundImageView = [[UIImageView alloc] initWithImage:bgImage];
_tipBackgroundImageView.frame = CGRectMake(0, -bgImage.size.height, bgImage.size.width, bgImage.size.height);
_tipBackgroundImageView.hidden = YES;
}
return _tipBackgroundImageView;
}
- (UILabel *)tipLabel {
if (!_tipLabel) {
_tipLabel = [[UILabel alloc] initWithFrame:self.tipBackgroundImageView.frame];
_tipLabel.textColor = [UIColor whiteColor];
_tipLabel.font = [UIFont systemFontOfSize:10 weight:UIFontWeightMedium];
_tipLabel.textAlignment = NSTextAlignmentCenter;
_tipLabel.hidden = YES;
}
return _tipLabel;
}
- (UIView *)trackView {
if (!_trackView) {
_trackView = [[UIView alloc] init];
_trackView.backgroundColor = [UIColor colorWithRed:103/255.f green:195/255.f blue:103/255.f alpha:1];
_trackView.hidden = YES;
}
return _trackView;
}
- (UIView *)middleLine {
if (!_middleLine) {
_middleLine = [[UIView alloc] initWithFrame:CGRectMake(CGRectGetWidth(self.bounds)/2.0 - 1, CGRectGetHeight(self.bounds)/2.0 - 4, 2, 8)];
_middleLine.backgroundColor = [UIColor whiteColor];
_middleLine.layer.masksToBounds = YES;
_middleLine.layer.cornerRadius = 1.0;
_middleLine.hidden = YES;
}
return _middleLine;
}
@end

View File

@@ -0,0 +1,15 @@
//
// FUSquareButton.h
// FULive
//
// Created by 孙慕 on 2018/8/28.
// Copyright © 2018年 L. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface FUSquareButton : UIButton
- (instancetype)initWithFrame:(CGRect)frame interval:(float)interval;
@end

View File

@@ -0,0 +1,63 @@
//
// FUSquareButton.m
// FULive
//
// Created by on 2018/8/28.
// Copyright © 2018 L. All rights reserved.
//
#import "FUSquareButton.h"
@interface FUSquareButton()
@property(nonatomic,assign) float interval;
@end
@implementation FUSquareButton
- (instancetype)initWithFrame:(CGRect)frame interval:(float)interval{
if (self = [super initWithFrame:frame]) {
_interval = interval;
self.titleLabel.textAlignment = NSTextAlignmentCenter;
self.titleLabel.font = [UIFont systemFontOfSize:14];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
_interval = 8;
self.titleLabel.textAlignment = NSTextAlignmentCenter;
self.titleLabel.font = [UIFont systemFontOfSize:14];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
_interval = 8;
self.titleLabel.textAlignment = NSTextAlignmentCenter;
self.titleLabel.font = [UIFont systemFontOfSize:14];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
CGRect frame = self.imageView.bounds;
CGRect frame1 = self.titleLabel.frame;
self.imageView.frame = frame;
CGPoint center = self.imageView.center;
center.x = self.frame.size.width * 0.5;
self.imageView.center = center;
frame1.origin.x = 0;
frame1.origin.y = CGRectGetMaxY(self.imageView.frame) + _interval;
frame1.size.height = 14;
frame1.size.width = self.bounds.size.width;;
self.titleLabel.frame = frame1;
}
@end

View File

@@ -0,0 +1,37 @@
//
// FUTipHUD.h
// FULiveDemo
//
// Created by 项林平 on 2021/4/12.
// Copyright © 2021 FaceUnity. All rights reserved.
//
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSUInteger, FUTipHUDPosition) {
FUTipHUDPositionTop,
FUTipHUDPositionCenter
};
NS_ASSUME_NONNULL_BEGIN
@interface FUTipHUD : NSObject
/// 文字提示默认3秒后自动消失
/// @param tipsString 文字
+ (void)showTips:(NSString *)tipsString;
/// 文字提示
/// @param tipsString 文字
/// @param delay 自动消失时间,单位: 秒
+ (void)showTips:(NSString *)tipsString dismissWithDelay:(NSTimeInterval)delay;
/// 文字提示
/// @param tipsString 文字
/// @param delay 自动消失时间,单位: 秒
/// @param position 显示位置默认为FUTipHUDPositionTop
+ (void)showTips:(NSString *)tipsString dismissWithDelay:(NSTimeInterval)delay position:(FUTipHUDPosition)position;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,72 @@
//
// FUTipHUD.m
// FULiveDemo
//
// Created by on 2021/4/12.
// Copyright © 2021 FaceUnity. All rights reserved.
//
#import "FUTipHUD.h"
#import "FUInsetsLabel.h"
@implementation FUTipHUD
+ (void)showTips:(NSString *)tipsString {
[self showTips:tipsString dismissWithDelay:3];
}
+ (void)showTips:(NSString *)tipsString dismissWithDelay:(NSTimeInterval)delay {
[self showTips:tipsString dismissWithDelay:delay position:FUTipHUDPositionTop];
}
+ (void)showTips:(NSString *)tipsString dismissWithDelay:(NSTimeInterval)delay position:(FUTipHUDPosition)position {
UIWindow *window = [UIApplication sharedApplication].delegate.window;
// label
NSArray<UIView *> *views = window.subviews;
[views enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj isMemberOfClass:[FUInsetsLabel class]]) {
[obj removeFromSuperview];
obj = nil;
}
}];
__block FUInsetsLabel *tipLabel = [[FUInsetsLabel alloc] initWithFrame:CGRectZero insets:UIEdgeInsetsMake(8, 20, 8, 20)];
tipLabel.backgroundColor = [UIColor colorWithRed:5/255.0 green:15/255.0 blue:20/255.0 alpha:0.74];
tipLabel.textColor = [UIColor whiteColor];
tipLabel.font = [UIFont systemFontOfSize:13];
tipLabel.numberOfLines = 0;
tipLabel.layer.masksToBounds = YES;
tipLabel.layer.cornerRadius = 4;
tipLabel.translatesAutoresizingMaskIntoConstraints = NO;
tipLabel.text = tipsString;
[window addSubview:tipLabel];
if (position == FUTipHUDPositionTop) {
CGFloat topConstant = 0;
if (@available(iOS 11.0, *)) {
topConstant = window.safeAreaInsets.top;
}
NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:tipLabel attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:window attribute:NSLayoutAttributeTop multiplier:1 constant:84 + topConstant];
[window addConstraint:topConstraint];
} else {
NSLayoutConstraint *centerYConstraint = [NSLayoutConstraint constraintWithItem:tipLabel attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:window attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
[window addConstraint:centerYConstraint];
}
CGFloat windowWidth = CGRectGetWidth(window.bounds);
NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:tipLabel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationLessThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:windowWidth - 40];
NSLayoutConstraint *centerXConstraint = [NSLayoutConstraint constraintWithItem:tipLabel attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:window attribute:NSLayoutAttributeCenterX multiplier:1 constant:0];
[window addConstraint:centerXConstraint];
[tipLabel addConstraint:widthConstraint];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.3 animations:^{
tipLabel.alpha = 0;
} completion:^(BOOL finished) {
[tipLabel removeFromSuperview];
tipLabel = nil;
}];
});
}
@end

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
# FUCommonUIComponent
通用UI组件

BIN
Example/SubModules/FURenderKit/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,628 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
25D108CE056FDC71944B8D96 /* Pods_FURenderKit_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DE66819F43636C81BFA273B /* Pods_FURenderKit_Example.framework */; };
6003F58E195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; };
6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58F195388D20070C39A /* CoreGraphics.framework */; };
6003F592195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
6003F598195388D20070C39A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F596195388D20070C39A /* InfoPlist.strings */; };
6003F59A195388D20070C39A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F599195388D20070C39A /* main.m */; };
6003F59E195388D20070C39A /* CALEBAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F59D195388D20070C39A /* CALEBAppDelegate.m */; };
6003F5A7195388D20070C39A /* CALEBViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F5A6195388D20070C39A /* CALEBViewController.m */; };
6003F5A9195388D20070C39A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5A8195388D20070C39A /* Images.xcassets */; };
6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F5AF195388D20070C39A /* XCTest.framework */; };
6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; };
6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5B8195388D20070C39A /* InfoPlist.strings */; };
6003F5BC195388D20070C39A /* Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F5BB195388D20070C39A /* Tests.m */; };
71719F9F1E33DC2100824A3D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 71719F9D1E33DC2100824A3D /* LaunchScreen.storyboard */; };
873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */; };
938CD1392A2747472CD1D0B6 /* Pods_FURenderKit_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F73A65B7B681BAD128B0C55A /* Pods_FURenderKit_Tests.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
6003F5B3195388D20070C39A /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 6003F582195388D10070C39A /* Project object */;
proxyType = 1;
remoteGlobalIDString = 6003F589195388D20070C39A;
remoteInfo = FURenderKit;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
16328503CF7647332B00EB76 /* Pods-FURenderKit_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FURenderKit_Example.debug.xcconfig"; path = "Target Support Files/Pods-FURenderKit_Example/Pods-FURenderKit_Example.debug.xcconfig"; sourceTree = "<group>"; };
3DE66819F43636C81BFA273B /* Pods_FURenderKit_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FURenderKit_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
403E0382A57DE0A2174B1A32 /* Pods-FURenderKit_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FURenderKit_Tests.debug.xcconfig"; path = "Target Support Files/Pods-FURenderKit_Tests/Pods-FURenderKit_Tests.debug.xcconfig"; sourceTree = "<group>"; };
5D7DB92367728FD88D73BA2D /* FURenderKit.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = FURenderKit.podspec; path = ../FURenderKit.podspec; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
6003F58A195388D20070C39A /* FURenderKit_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FURenderKit_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; };
6003F58F195388D20070C39A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
6003F591195388D20070C39A /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
6003F595195388D20070C39A /* FURenderKit-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "FURenderKit-Info.plist"; sourceTree = "<group>"; };
6003F597195388D20070C39A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
6003F599195388D20070C39A /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
6003F59B195388D20070C39A /* FURenderKit-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FURenderKit-Prefix.pch"; sourceTree = "<group>"; };
6003F59C195388D20070C39A /* CALEBAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CALEBAppDelegate.h; sourceTree = "<group>"; };
6003F59D195388D20070C39A /* CALEBAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CALEBAppDelegate.m; sourceTree = "<group>"; };
6003F5A5195388D20070C39A /* CALEBViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CALEBViewController.h; sourceTree = "<group>"; };
6003F5A6195388D20070C39A /* CALEBViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CALEBViewController.m; sourceTree = "<group>"; };
6003F5A8195388D20070C39A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
6003F5AE195388D20070C39A /* FURenderKit_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FURenderKit_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
6003F5AF195388D20070C39A /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
6003F5B7195388D20070C39A /* Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = "<group>"; };
6003F5B9195388D20070C39A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
6003F5BB195388D20070C39A /* Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Tests.m; sourceTree = "<group>"; };
606FC2411953D9B200FFA9A0 /* Tests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Tests-Prefix.pch"; sourceTree = "<group>"; };
71719F9E1E33DC2100824A3D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
7DDD0571106B74DD57603351 /* Pods-FURenderKit_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FURenderKit_Tests.release.xcconfig"; path = "Target Support Files/Pods-FURenderKit_Tests/Pods-FURenderKit_Tests.release.xcconfig"; sourceTree = "<group>"; };
873B8AEA1B1F5CCA007FD442 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Main.storyboard; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
B4A9EEC998BB33EDC1011552 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
D2AC8D5C01FEE0E1C47CF521 /* Pods-FURenderKit_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FURenderKit_Example.release.xcconfig"; path = "Target Support Files/Pods-FURenderKit_Example/Pods-FURenderKit_Example.release.xcconfig"; sourceTree = "<group>"; };
EF6578F88748EF764D8A0E2C /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
F73A65B7B681BAD128B0C55A /* Pods_FURenderKit_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FURenderKit_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
6003F587195388D20070C39A /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */,
6003F592195388D20070C39A /* UIKit.framework in Frameworks */,
6003F58E195388D20070C39A /* Foundation.framework in Frameworks */,
25D108CE056FDC71944B8D96 /* Pods_FURenderKit_Example.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
6003F5AB195388D20070C39A /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */,
6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */,
6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */,
938CD1392A2747472CD1D0B6 /* Pods_FURenderKit_Tests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
6003F581195388D10070C39A = {
isa = PBXGroup;
children = (
60FF7A9C1954A5C5007DD14C /* Podspec Metadata */,
6003F593195388D20070C39A /* Example for FURenderKit */,
6003F5B5195388D20070C39A /* Tests */,
6003F58C195388D20070C39A /* Frameworks */,
6003F58B195388D20070C39A /* Products */,
A2FD88596E1021E2ED41BA65 /* Pods */,
);
sourceTree = "<group>";
};
6003F58B195388D20070C39A /* Products */ = {
isa = PBXGroup;
children = (
6003F58A195388D20070C39A /* FURenderKit_Example.app */,
6003F5AE195388D20070C39A /* FURenderKit_Tests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
6003F58C195388D20070C39A /* Frameworks */ = {
isa = PBXGroup;
children = (
6003F58D195388D20070C39A /* Foundation.framework */,
6003F58F195388D20070C39A /* CoreGraphics.framework */,
6003F591195388D20070C39A /* UIKit.framework */,
6003F5AF195388D20070C39A /* XCTest.framework */,
3DE66819F43636C81BFA273B /* Pods_FURenderKit_Example.framework */,
F73A65B7B681BAD128B0C55A /* Pods_FURenderKit_Tests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
6003F593195388D20070C39A /* Example for FURenderKit */ = {
isa = PBXGroup;
children = (
6003F59C195388D20070C39A /* CALEBAppDelegate.h */,
6003F59D195388D20070C39A /* CALEBAppDelegate.m */,
873B8AEA1B1F5CCA007FD442 /* Main.storyboard */,
6003F5A5195388D20070C39A /* CALEBViewController.h */,
6003F5A6195388D20070C39A /* CALEBViewController.m */,
71719F9D1E33DC2100824A3D /* LaunchScreen.storyboard */,
6003F5A8195388D20070C39A /* Images.xcassets */,
6003F594195388D20070C39A /* Supporting Files */,
);
name = "Example for FURenderKit";
path = FURenderKit;
sourceTree = "<group>";
};
6003F594195388D20070C39A /* Supporting Files */ = {
isa = PBXGroup;
children = (
6003F595195388D20070C39A /* FURenderKit-Info.plist */,
6003F596195388D20070C39A /* InfoPlist.strings */,
6003F599195388D20070C39A /* main.m */,
6003F59B195388D20070C39A /* FURenderKit-Prefix.pch */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
6003F5B5195388D20070C39A /* Tests */ = {
isa = PBXGroup;
children = (
6003F5BB195388D20070C39A /* Tests.m */,
6003F5B6195388D20070C39A /* Supporting Files */,
);
path = Tests;
sourceTree = "<group>";
};
6003F5B6195388D20070C39A /* Supporting Files */ = {
isa = PBXGroup;
children = (
6003F5B7195388D20070C39A /* Tests-Info.plist */,
6003F5B8195388D20070C39A /* InfoPlist.strings */,
606FC2411953D9B200FFA9A0 /* Tests-Prefix.pch */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
60FF7A9C1954A5C5007DD14C /* Podspec Metadata */ = {
isa = PBXGroup;
children = (
5D7DB92367728FD88D73BA2D /* FURenderKit.podspec */,
EF6578F88748EF764D8A0E2C /* README.md */,
B4A9EEC998BB33EDC1011552 /* LICENSE */,
);
name = "Podspec Metadata";
sourceTree = "<group>";
};
A2FD88596E1021E2ED41BA65 /* Pods */ = {
isa = PBXGroup;
children = (
16328503CF7647332B00EB76 /* Pods-FURenderKit_Example.debug.xcconfig */,
D2AC8D5C01FEE0E1C47CF521 /* Pods-FURenderKit_Example.release.xcconfig */,
403E0382A57DE0A2174B1A32 /* Pods-FURenderKit_Tests.debug.xcconfig */,
7DDD0571106B74DD57603351 /* Pods-FURenderKit_Tests.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
6003F589195388D20070C39A /* FURenderKit_Example */ = {
isa = PBXNativeTarget;
buildConfigurationList = 6003F5BF195388D20070C39A /* Build configuration list for PBXNativeTarget "FURenderKit_Example" */;
buildPhases = (
63CAF193CF27A4682A308E4D /* [CP] Check Pods Manifest.lock */,
6003F586195388D20070C39A /* Sources */,
6003F587195388D20070C39A /* Frameworks */,
6003F588195388D20070C39A /* Resources */,
04234FB4C1CACC9C1F004127 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = FURenderKit_Example;
productName = FURenderKit;
productReference = 6003F58A195388D20070C39A /* FURenderKit_Example.app */;
productType = "com.apple.product-type.application";
};
6003F5AD195388D20070C39A /* FURenderKit_Tests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 6003F5C2195388D20070C39A /* Build configuration list for PBXNativeTarget "FURenderKit_Tests" */;
buildPhases = (
5A56B9B9057B0D35BB0911A6 /* [CP] Check Pods Manifest.lock */,
6003F5AA195388D20070C39A /* Sources */,
6003F5AB195388D20070C39A /* Frameworks */,
6003F5AC195388D20070C39A /* Resources */,
765D239FA97D8E8C7588ACA9 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
6003F5B4195388D20070C39A /* PBXTargetDependency */,
);
name = FURenderKit_Tests;
productName = FURenderKitTests;
productReference = 6003F5AE195388D20070C39A /* FURenderKit_Tests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
6003F582195388D10070C39A /* Project object */ = {
isa = PBXProject;
attributes = {
CLASSPREFIX = CALEB;
LastUpgradeCheck = 0720;
ORGANIZATIONNAME = Caleb;
TargetAttributes = {
6003F5AD195388D20070C39A = {
TestTargetID = 6003F589195388D20070C39A;
};
};
};
buildConfigurationList = 6003F585195388D10070C39A /* Build configuration list for PBXProject "FURenderKit" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
Base,
);
mainGroup = 6003F581195388D10070C39A;
productRefGroup = 6003F58B195388D20070C39A /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
6003F589195388D20070C39A /* FURenderKit_Example */,
6003F5AD195388D20070C39A /* FURenderKit_Tests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
6003F588195388D20070C39A /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */,
71719F9F1E33DC2100824A3D /* LaunchScreen.storyboard in Resources */,
6003F5A9195388D20070C39A /* Images.xcassets in Resources */,
6003F598195388D20070C39A /* InfoPlist.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
6003F5AC195388D20070C39A /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
04234FB4C1CACC9C1F004127 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-FURenderKit_Example/Pods-FURenderKit_Example-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/FURenderKit/FURenderKit.framework",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FURenderKit.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-FURenderKit_Example/Pods-FURenderKit_Example-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
5A56B9B9057B0D35BB0911A6 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-FURenderKit_Tests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
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;
};
63CAF193CF27A4682A308E4D /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-FURenderKit_Example-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
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;
};
765D239FA97D8E8C7588ACA9 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-FURenderKit_Tests/Pods-FURenderKit_Tests-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/Expecta/Expecta.framework",
"${BUILT_PRODUCTS_DIR}/Expecta+Snapshots/Expecta_Snapshots.framework",
"${BUILT_PRODUCTS_DIR}/FBSnapshotTestCase/FBSnapshotTestCase.framework",
"${BUILT_PRODUCTS_DIR}/Specta/Specta.framework",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Expecta.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Expecta_Snapshots.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBSnapshotTestCase.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Specta.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-FURenderKit_Tests/Pods-FURenderKit_Tests-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
6003F586195388D20070C39A /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
6003F59E195388D20070C39A /* CALEBAppDelegate.m in Sources */,
6003F5A7195388D20070C39A /* CALEBViewController.m in Sources */,
6003F59A195388D20070C39A /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
6003F5AA195388D20070C39A /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
6003F5BC195388D20070C39A /* Tests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
6003F5B4195388D20070C39A /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 6003F589195388D20070C39A /* FURenderKit_Example */;
targetProxy = 6003F5B3195388D20070C39A /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
6003F596195388D20070C39A /* InfoPlist.strings */ = {
isa = PBXVariantGroup;
children = (
6003F597195388D20070C39A /* en */,
);
name = InfoPlist.strings;
sourceTree = "<group>";
};
6003F5B8195388D20070C39A /* InfoPlist.strings */ = {
isa = PBXVariantGroup;
children = (
6003F5B9195388D20070C39A /* en */,
);
name = InfoPlist.strings;
sourceTree = "<group>";
};
71719F9D1E33DC2100824A3D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
71719F9E1E33DC2100824A3D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
6003F5BD195388D20070C39A /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
6003F5BE195388D20070C39A /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
6003F5C0195388D20070C39A /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 16328503CF7647332B00EB76 /* Pods-FURenderKit_Example.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "FURenderKit/FURenderKit-Prefix.pch";
INFOPLIST_FILE = "FURenderKit/FURenderKit-Info.plist";
MODULE_NAME = ExampleApp;
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.0;
WRAPPER_EXTENSION = app;
};
name = Debug;
};
6003F5C1195388D20070C39A /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = D2AC8D5C01FEE0E1C47CF521 /* Pods-FURenderKit_Example.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "FURenderKit/FURenderKit-Prefix.pch";
INFOPLIST_FILE = "FURenderKit/FURenderKit-Info.plist";
MODULE_NAME = ExampleApp;
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.0;
WRAPPER_EXTENSION = app;
};
name = Release;
};
6003F5C3195388D20070C39A /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 403E0382A57DE0A2174B1A32 /* Pods-FURenderKit_Tests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
FRAMEWORK_SEARCH_PATHS = (
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
"$(inherited)",
"$(DEVELOPER_FRAMEWORKS_DIR)",
);
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch";
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
INFOPLIST_FILE = "Tests/Tests-Info.plist";
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FURenderKit_Example.app/FURenderKit_Example";
WRAPPER_EXTENSION = xctest;
};
name = Debug;
};
6003F5C4195388D20070C39A /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7DDD0571106B74DD57603351 /* Pods-FURenderKit_Tests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
FRAMEWORK_SEARCH_PATHS = (
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
"$(inherited)",
"$(DEVELOPER_FRAMEWORKS_DIR)",
);
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch";
INFOPLIST_FILE = "Tests/Tests-Info.plist";
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FURenderKit_Example.app/FURenderKit_Example";
WRAPPER_EXTENSION = xctest;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
6003F585195388D10070C39A /* Build configuration list for PBXProject "FURenderKit" */ = {
isa = XCConfigurationList;
buildConfigurations = (
6003F5BD195388D20070C39A /* Debug */,
6003F5BE195388D20070C39A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
6003F5BF195388D20070C39A /* Build configuration list for PBXNativeTarget "FURenderKit_Example" */ = {
isa = XCConfigurationList;
buildConfigurations = (
6003F5C0195388D20070C39A /* Debug */,
6003F5C1195388D20070C39A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
6003F5C2195388D20070C39A /* Build configuration list for PBXNativeTarget "FURenderKit_Tests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
6003F5C3195388D20070C39A /* Debug */,
6003F5C4195388D20070C39A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 6003F582195388D10070C39A /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:FURenderKit.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0720"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6003F589195388D20070C39A"
BuildableName = "FURenderKit_Example.app"
BlueprintName = "FURenderKit_Example"
ReferencedContainer = "container:FURenderKit.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6003F5AD195388D20070C39A"
BuildableName = "FURenderKit_Tests.xctest"
BlueprintName = "FURenderKit_Tests"
ReferencedContainer = "container:FURenderKit.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6003F589195388D20070C39A"
BuildableName = "FURenderKit_Example.app"
BlueprintName = "FURenderKit_Example"
ReferencedContainer = "container:FURenderKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6003F589195388D20070C39A"
BuildableName = "FURenderKit_Example.app"
BlueprintName = "FURenderKit_Example"
ReferencedContainer = "container:FURenderKit.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6003F589195388D20070C39A"
BuildableName = "FURenderKit_Example.app"
BlueprintName = "FURenderKit_Example"
ReferencedContainer = "container:FURenderKit.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:FURenderKit.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="whP-gf-Uak">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="wQg-tq-qST">
<objects>
<viewController id="whP-gf-Uak" customClass="CALEBViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="uEw-UM-LJ8"/>
<viewControllerLayoutGuide type="bottom" id="Mvr-aV-6Um"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="TpU-gO-2f1">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="tc2-Qw-aMS" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="305" y="433"/>
</scene>
</scenes>
</document>

View File

@@ -0,0 +1,15 @@
//
// CALEBAppDelegate.h
// FURenderKit
//
// Created by Caleb on 08/05/2025.
// Copyright (c) 2025 Caleb. All rights reserved.
//
@import UIKit;
@interface CALEBAppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end

View File

@@ -0,0 +1,46 @@
//
// CALEBAppDelegate.m
// FURenderKit
//
// Created by Caleb on 08/05/2025.
// Copyright (c) 2025 Caleb. All rights reserved.
//
#import "CALEBAppDelegate.h"
@implementation CALEBAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application
{
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
- (void)applicationWillTerminate:(UIApplication *)application
{
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
@end

View File

@@ -0,0 +1,13 @@
//
// CALEBViewController.h
// FURenderKit
//
// Created by Caleb on 08/05/2025.
// Copyright (c) 2025 Caleb. All rights reserved.
//
@import UIKit;
@interface CALEBViewController : UIViewController
@end

View File

@@ -0,0 +1,29 @@
//
// CALEBViewController.m
// FURenderKit
//
// Created by Caleb on 08/05/2025.
// Copyright (c) 2025 Caleb. All rights reserved.
//
#import "CALEBViewController.h"
@interface CALEBViewController ()
@end
@implementation CALEBViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,16 @@
//
// Prefix header
//
// The contents of this file are implicitly included at the beginning of every source file.
//
#import <Availability.h>
#ifndef __IPHONE_5_0
#warning "This project uses features only available in iOS SDK 5.0 and later."
#endif
#ifdef __OBJC__
@import UIKit;
@import Foundation;
#endif

View File

@@ -0,0 +1,98 @@
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "83.5x83.5",
"scale" : "2x"
},
{
"idiom" : "ios-marketing",
"size" : "1024x1024",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@@ -0,0 +1,2 @@
/* Localized versions of Info.plist keys */

View File

@@ -0,0 +1,17 @@
//
// main.m
// FURenderKit
//
// Created by Caleb on 08/05/2025.
// Copyright (c) 2025 Caleb. All rights reserved.
//
@import UIKit;
#import "CALEBAppDelegate.h"
int main(int argc, char * argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([CALEBAppDelegate class]));
}
}

View File

@@ -0,0 +1,16 @@
use_frameworks!
platform :ios, '10.0'
target 'FURenderKit_Example' do
pod 'FURenderKit', :path => '../'
target 'FURenderKit_Tests' do
inherit! :search_paths
pod 'Specta'
pod 'Expecta'
pod 'FBSnapshotTestCase'
pod 'Expecta+Snapshots'
end
end

View File

@@ -0,0 +1,42 @@
PODS:
- Expecta (1.0.6)
- "Expecta+Snapshots (3.1.1)":
- Expecta (~> 1.0)
- FBSnapshotTestCase/Core (~> 2.0)
- Specta (~> 1.0)
- FBSnapshotTestCase (2.1.4):
- FBSnapshotTestCase/SwiftSupport (= 2.1.4)
- FBSnapshotTestCase/Core (2.1.4)
- FBSnapshotTestCase/SwiftSupport (2.1.4):
- FBSnapshotTestCase/Core
- FURenderKit (0.1.0)
- Specta (1.0.7)
DEPENDENCIES:
- Expecta
- "Expecta+Snapshots"
- FBSnapshotTestCase
- FURenderKit (from `../`)
- Specta
SPEC REPOS:
trunk:
- Expecta
- "Expecta+Snapshots"
- FBSnapshotTestCase
- Specta
EXTERNAL SOURCES:
FURenderKit:
:path: "../"
SPEC CHECKSUMS:
Expecta: 3b6bd90a64b9a1dcb0b70aa0e10a7f8f631667d5
"Expecta+Snapshots": dcff217eef506dabd6dfdc7864ea2da321fafbb8
FBSnapshotTestCase: 094f9f314decbabe373b87cc339bea235a63e07a
FURenderKit: 2ca084d9811616f9bb1936160a57d77acd43357b
Specta: 3e1bd89c3517421982dc4d1c992503e48bd5fe66
PODFILE CHECKSUM: bd8f39f5f1fa7e2c55f59c911d2c5a464617dbd0
COCOAPODS: 1.16.2

View File

@@ -0,0 +1,18 @@
#import <CoreGraphics/CoreGraphics.h>
#import <Expecta/Expecta.h>
#import "ExpectaObject+FBSnapshotTest.h"
@interface EXPExpectFBSnapshotTest : NSObject
@end
/// Set the default folder for image tests to run in
extern void setGlobalReferenceImageDir(char *reference);
EXPMatcherInterface(haveValidSnapshot, (void));
EXPMatcherInterface(recordSnapshot, (void));
EXPMatcherInterface(haveValidSnapshotNamed, (NSString *snapshot));
EXPMatcherInterface(recordSnapshotNamed, (NSString *snapshot));
EXPMatcherInterface(haveValidSnapshotNamedWithTolerance, (NSString *snapshot, CGFloat tolerance));
EXPMatcherInterface(haveValidSnapshotWithTolerance, (CGFloat tolerance));

View File

@@ -0,0 +1,311 @@
#import "EXPMatchers+FBSnapshotTest.h"
#import <Expecta/EXPMatcherHelpers.h>
#import <FBSnapshotTestCase/FBSnapshotTestController.h>
@interface EXPExpectFBSnapshotTest()
@property (nonatomic, strong) NSString *referenceImagesDirectory;
@end
@implementation EXPExpectFBSnapshotTest
+ (id)instance
{
static EXPExpectFBSnapshotTest *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
+ (BOOL)compareSnapshotOfViewOrLayer:(id)viewOrLayer snapshot:(NSString *)snapshot testCase:(id)testCase record:(BOOL)record referenceDirectory:(NSString *)referenceDirectory tolerance:(CGFloat)tolerance error:(NSError **)error
{
FBSnapshotTestController *snapshotController = [[FBSnapshotTestController alloc] initWithTestClass:[testCase class]];
snapshotController.recordMode = record;
snapshotController.referenceImagesDirectory = referenceDirectory;
snapshotController.usesDrawViewHierarchyInRect = [Expecta usesDrawViewHierarchyInRect];
snapshotController.deviceAgnostic = [Expecta isDeviceAgnostic];
if (! snapshotController.referenceImagesDirectory) {
[NSException raise:@"Missing value for referenceImagesDirectory" format:@"Call [[EXPExpectFBSnapshotTest instance] setReferenceImagesDirectory"];
}
return [snapshotController compareSnapshotOfViewOrLayer:viewOrLayer
selector:NSSelectorFromString(snapshot)
identifier:nil
tolerance:tolerance
error:error];
}
+ (NSString *)combinedError:(NSString *)message test:(NSString *)test error:(NSError *)error
{
NSAssert(message, @"missing message");
NSAssert(test, @"missing test name");
NSMutableArray *ary = [NSMutableArray array];
[ary addObject:[NSString stringWithFormat:@"%@ %@", message, test]];
for(NSString *key in error.userInfo.keyEnumerator) {
[ary addObject:[NSString stringWithFormat:@" %@: %@", key, [error.userInfo valueForKey:key]]];
}
return [ary componentsJoinedByString:@"\n"];
}
@end
void setGlobalReferenceImageDir(char *reference) {
NSString *referenceImagesDirectory = [NSString stringWithFormat:@"%s", reference];
[[EXPExpectFBSnapshotTest instance] setReferenceImagesDirectory:referenceImagesDirectory];
};
@interface EXPExpect(ReferenceDirExtension)
- (NSString *)_getDefaultReferenceDirectory;
@end
@implementation EXPExpect(ReferenceDirExtension)
- (NSString *)_getDefaultReferenceDirectory
{
NSString *globalReference = [[EXPExpectFBSnapshotTest instance] referenceImagesDirectory];
if (globalReference) {
return globalReference;
}
// Search the test file's path to find the first folder with the substring "tests"
// then append "/ReferenceImages" and use that
NSString *testFileName = [NSString stringWithCString:self.fileName encoding:NSUTF8StringEncoding];
NSArray *pathComponents = [testFileName pathComponents];
NSString *firstFolderFound = nil;
for (NSString *folder in pathComponents.reverseObjectEnumerator) {
if ([folder.lowercaseString rangeOfString:@"tests"].location != NSNotFound) {
NSArray *folderPathComponents = [pathComponents subarrayWithRange:NSMakeRange(0, [pathComponents indexOfObject:folder] + 1)];
NSString *referenceImagesPath = [NSString stringWithFormat:@"%@/ReferenceImages", [folderPathComponents componentsJoinedByString:@"/"]];
if (!firstFolderFound) {
firstFolderFound = referenceImagesPath;
}
BOOL isDirectory = NO;
BOOL referenceDirExists = [[NSFileManager defaultManager] fileExistsAtPath:referenceImagesPath isDirectory:&isDirectory];
// if the folder exists, this is the reference dir for sure
if (referenceDirExists && isDirectory) {
return referenceImagesPath;
}
}
}
// if a reference folder wasn't found, we should create one
if (firstFolderFound) {
return firstFolderFound;
}
[NSException raise:@"Could not infer reference image folder" format:@"You should provide a reference dir using setGlobalReferenceImageDir(FB_REFERENCE_IMAGE_DIR);"];
return nil;
}
@end
#import <Specta/Specta.h>
#import <Specta/SpectaUtility.h>
#import <Specta/SPTExample.h>
NSString *sanitizedTestPath();
NSString *sanitizedTestPath(){
id compiledExample = [[NSThread currentThread] threadDictionary][@"SPTCurrentSpec"]; // SPTSpec
NSString *name;
if ([compiledExample respondsToSelector:@selector(name)]) {
// Specta 0.3 syntax
name = [compiledExample performSelector:@selector(name)];
} else if ([compiledExample respondsToSelector:@selector(fileName)]) {
// Specta 0.2 syntax
name = [compiledExample performSelector:@selector(fileName)];
}
name = [[[[name componentsSeparatedByString:@" test_"] lastObject] stringByReplacingOccurrencesOfString:@"__" withString:@"_"] stringByReplacingOccurrencesOfString:@"]" withString:@""];
return name;
}
EXPMatcherImplementationBegin(haveValidSnapshotWithTolerance, (CGFloat tolerance)){
__block NSError *error = nil;
prerequisite(^BOOL{
return actual != nil;
});
match(^BOOL{
NSString *referenceImageDir = [self _getDefaultReferenceDirectory];
NSString *name = sanitizedTestPath();
if ([actual isKindOfClass:UIViewController.class]) {
[actual beginAppearanceTransition:YES animated:NO];
[actual endAppearanceTransition];
actual = [actual view];
}
return [EXPExpectFBSnapshotTest compareSnapshotOfViewOrLayer:actual snapshot:name testCase:[self testCase] record:NO referenceDirectory:referenceImageDir tolerance:tolerance error:&error];
});
failureMessageForTo(^NSString *{
if (!actual) {
return [EXPExpectFBSnapshotTest combinedError:@"Nil was passed into haveValidSnapshot." test:sanitizedTestPath() error:nil];
}
return [EXPExpectFBSnapshotTest combinedError:@"expected a matching snapshot in" test:sanitizedTestPath() error:error];
});
failureMessageForNotTo(^NSString *{
return [EXPExpectFBSnapshotTest combinedError:@"expected to not have a matching snapshot in" test:sanitizedTestPath() error:error];
});
}
EXPMatcherImplementationEnd
EXPMatcherImplementationBegin(haveValidSnapshot, (void)) {
return self.haveValidSnapshotWithTolerance(0);
}
EXPMatcherImplementationEnd
EXPMatcherImplementationBegin(recordSnapshot, (void)) {
__block NSError *error = nil;
BOOL actualIsViewLayerOrViewController = ([actual isKindOfClass:UIView.class] || [actual isKindOfClass:CALayer.class] || [actual isKindOfClass:UIViewController.class]);
prerequisite(^BOOL{
return actual != nil && actualIsViewLayerOrViewController;
});
match(^BOOL{
NSString *referenceImageDir = [self _getDefaultReferenceDirectory];
// For view controllers do the viewWill/viewDid dance, then pass view through
if ([actual isKindOfClass:UIViewController.class]) {
[actual beginAppearanceTransition:YES animated:NO];
[actual endAppearanceTransition];
actual = [actual view];
}
[EXPExpectFBSnapshotTest compareSnapshotOfViewOrLayer:actual snapshot:sanitizedTestPath() testCase:[self testCase] record:YES referenceDirectory:referenceImageDir tolerance:0 error:&error];
return NO;
});
failureMessageForTo(^NSString *{
if (!actual) {
return [EXPExpectFBSnapshotTest combinedError:@"Nil was passed into recordSnapshot." test:sanitizedTestPath() error:nil];
}
if (!actualIsViewLayerOrViewController) {
return [EXPExpectFBSnapshotTest combinedError:@"Expected a View, Layer or View Controller." test:sanitizedTestPath() error:nil];
}
if (error) {
return [EXPExpectFBSnapshotTest combinedError:@"expected to record a snapshot in" test:sanitizedTestPath() error:error];
} else {
return [NSString stringWithFormat:@"snapshot %@ successfully recorded, replace recordSnapshot with a check", sanitizedTestPath()];
}
});
failureMessageForNotTo(^NSString *{
if (error) {
return [EXPExpectFBSnapshotTest combinedError:@"expected to record a snapshot in" test:sanitizedTestPath() error:error];
} else {
return [NSString stringWithFormat:@"snapshot %@ successfully recorded, replace recordSnapshot with a check", sanitizedTestPath()];
}
});
}
EXPMatcherImplementationEnd
EXPMatcherImplementationBegin(haveValidSnapshotNamedWithTolerance, (NSString *snapshot, CGFloat tolerance)) {
BOOL snapshotIsNil = (snapshot == nil);
__block NSError *error = nil;
prerequisite(^BOOL{
return actual != nil && !(snapshotIsNil);
});
match(^BOOL{
NSString *referenceImageDir = [self _getDefaultReferenceDirectory];
if ([actual isKindOfClass:UIViewController.class]) {
[actual beginAppearanceTransition:YES animated:NO];
[actual endAppearanceTransition];
actual = [actual view];
}
return [EXPExpectFBSnapshotTest compareSnapshotOfViewOrLayer:actual snapshot:snapshot testCase:[self testCase] record:NO referenceDirectory:referenceImageDir tolerance:tolerance error:&error];
});
failureMessageForTo(^NSString *{
if (!actual) {
return [EXPExpectFBSnapshotTest combinedError:@"Nil was passed into haveValidSnapshotNamed." test:sanitizedTestPath() error:nil];
}
return [EXPExpectFBSnapshotTest combinedError:@"expected a matching snapshot named" test:snapshot error:error];
});
failureMessageForNotTo(^NSString *{
return [EXPExpectFBSnapshotTest combinedError:@"expected not to have a matching snapshot named" test:snapshot error:error];
});
}
EXPMatcherImplementationEnd
EXPMatcherImplementationBegin(haveValidSnapshotNamed, (NSString *snapshot)) {
return self.haveValidSnapshotNamedWithTolerance(snapshot, 0);
}
EXPMatcherImplementationEnd
EXPMatcherImplementationBegin(recordSnapshotNamed, (NSString *snapshot)) {
BOOL snapshotExists = (snapshot != nil);
BOOL actualIsViewLayerOrViewController = ([actual isKindOfClass:UIView.class] || [actual isKindOfClass:CALayer.class] || [actual isKindOfClass:UIViewController.class]);
__block NSError *error = nil;
id actualRef = actual;
prerequisite(^BOOL{
return actualRef != nil && snapshotExists && actualIsViewLayerOrViewController;
});
match(^BOOL{
NSString *referenceImageDir = [self _getDefaultReferenceDirectory];
// For view controllers do the viewWill/viewDid dance, then pass view through
if ([actual isKindOfClass:UIViewController.class]) {
[actual beginAppearanceTransition:YES animated:NO];
[actual endAppearanceTransition];
actual = [actual view];
}
[EXPExpectFBSnapshotTest compareSnapshotOfViewOrLayer:actual snapshot:snapshot testCase:[self testCase] record:YES referenceDirectory:referenceImageDir tolerance:0 error:&error];
return NO;
});
failureMessageForTo(^NSString *{
if (!actual) {
return [EXPExpectFBSnapshotTest combinedError:@"Nil was passed into recordSnapshotNamed." test:sanitizedTestPath() error:nil];
}
if (!actualIsViewLayerOrViewController) {
return [EXPExpectFBSnapshotTest combinedError:@"Expected a View, Layer or View Controller." test:snapshot error:nil];
}
if (error) {
return [EXPExpectFBSnapshotTest combinedError:@"expected to record a matching snapshot named" test:snapshot error:error];
} else {
return [NSString stringWithFormat:@"snapshot %@ successfully recorded, replace recordSnapshot with a check", snapshot];
}
});
failureMessageForNotTo(^NSString *{
if (!actualIsViewLayerOrViewController) {
return [EXPExpectFBSnapshotTest combinedError:@"Expected a View, Layer or View Controller." test:snapshot error:nil];
}
if (error) {
return [EXPExpectFBSnapshotTest combinedError:@"expected to record a matching snapshot named" test:snapshot error:error];
} else {
return [NSString stringWithFormat:@"snapshot %@ successfully recorded, replace recordSnapshot with a check", snapshot];
}
});
}
EXPMatcherImplementationEnd

View File

@@ -0,0 +1,21 @@
//
// ExpectaObject+FBSnapshotTest.h
// Expecta+Snapshots
//
// Created by John Boiles on 8/3/15.
// Copyright (c) 2015 Expecta+Snapshots All rights reserved.
//
#import <Expecta/ExpectaObject.h>
@interface Expecta (FBSnapshotTest)
+ (void)setUsesDrawViewHierarchyInRect:(BOOL)usesDrawViewHierarchyInRect;
+ (BOOL)usesDrawViewHierarchyInRect;
+ (void)setDeviceAgnostic:(BOOL)deviceAgnostic;
+ (BOOL)isDeviceAgnostic;
@end

View File

@@ -0,0 +1,36 @@
//
// ExpectaObject+FBSnapshotTest.m
// Expecta+Snapshots
//
// Created by John Boiles on 8/3/15.
// Copyright (c) 2015 Expecta+Snapshots All rights reserved.
//
#import "ExpectaObject+FBSnapshotTest.h"
#import <objc/runtime.h>
static NSString const *kUsesDrawViewHierarchyInRectKey = @"ExpectaObject+FBSnapshotTest.usesDrawViewHierarchyInRect";
@implementation Expecta (FBSnapshotTest)
+ (void)setUsesDrawViewHierarchyInRect:(BOOL)usesDrawViewHierarchyInRect {
objc_setAssociatedObject(self, (__bridge const void *)(kUsesDrawViewHierarchyInRectKey), @(usesDrawViewHierarchyInRect), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
+ (BOOL)usesDrawViewHierarchyInRect {
NSNumber *usesDrawViewHierarchyInRect = objc_getAssociatedObject(self, (__bridge const void *)(kUsesDrawViewHierarchyInRectKey));
return usesDrawViewHierarchyInRect.boolValue;
}
+ (void)setDeviceAgnostic:(BOOL)deviceAgnostic
{
objc_setAssociatedObject(self, @selector(isDeviceAgnostic), @(deviceAgnostic), OBJC_ASSOCIATION_ASSIGN);
}
+ (BOOL)isDeviceAgnostic
{
NSNumber *isDeviceAgnostic = objc_getAssociatedObject(self, @selector(isDeviceAgnostic));
return isDeviceAgnostic.boolValue;
}
@end

View File

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

View File

@@ -0,0 +1,96 @@
Expecta Matchers for FBSnapshotTestCase
=======================================
[Expecta](https://github.com/specta/expecta) matchers for [ios-snapshot-test-case](https://github.com/facebook/ios-snapshot-test-case).
[![Build Status](https://travis-ci.org/dblock/ios-snapshot-test-case-expecta.svg)](https://travis-ci.org/dblock/ios-snapshot-test-case-expecta)
### Usage
Add `Expecta+Snapshots` to your Podfile, the latest `FBSnapshotTestCase` will come in as a dependency.
``` ruby
pod 'Expecta+Snapshots'
```
### App setup
Use `expect(view).to.recordSnapshotNamed(@"unique snapshot name")` to record a snapshot and `expect(view).to.haveValidSnapshotNamed(@"unique snapshot name")` to check it.
If you project was compiled with Specta included, you have two extra methods that use the spec hierarchy to generate the snapshot name for you: `recordSnapshot()` and `haveValidSnapshot()`. You should only call these once per `it()` block.
If you need the `usesDrawViewHierarchyInRect` property in order to correctly render UIVisualEffect, UIAppearance and Size Classes, call `[Expecta setUsesDrawViewHierarchyInRect:NO];` inside `beforeAll`.
``` Objective-C
#define EXP_SHORTHAND
#include <Specta/Specta.h>
#include <Expecta/Expecta.h>
#include <Expecta+Snapshots/EXPMatchers+FBSnapshotTest.h>
#include "FBExampleView.h"
SpecBegin(FBExampleView)
describe(@"manual matching", ^{
it(@"matches view", ^{
FBExampleView *view = [[FBExampleView alloc] initWithFrame:CGRectMake(0, 0, 64, 64)];
expect(view).to.recordSnapshotNamed(@"FBExampleView");
expect(view).to.haveValidSnapshotNamed(@"FBExampleView");
});
it(@"doesn't match a view", ^{
FBExampleView *view = [[FBExampleView alloc] initWithFrame:CGRectMake(0, 0, 64, 64)];
expect(view).toNot.haveValidSnapshotNamed(@"FBExampleViewDoesNotExist");
});
});
describe(@"test name derived matching", ^{
it(@"matches view", ^{
FBExampleView *view = [[FBExampleView alloc] initWithFrame:CGRectMake(0, 0, 64, 64)];
expect(view).to.recordSnapshot();
expect(view).to.haveValidSnapshot();
});
it(@"doesn't match a view", ^{
FBExampleView *view = [[FBExampleView alloc] initWithFrame:CGRectMake(0, 0, 64, 64)];
expect(view).toNot.haveValidSnapshot();
});
});
SpecEnd
```
### Approximation support
If for some reason you want to specify a tolerance for your test, you can use either named or unnamed matchers, where the `tolerance` parameter is a `CGFloat` in the interval `[0, 1]` and it represents the minimum ratio of unmatched points by the total number of points in your snapshot. In the example below, a tolerance of `0.01` means our `view` could be up to `1%` different from the reference image.
``` Objective-C
expect(view).to.haveValidSnapshotWithTolerance(0.01);
expect(view).to.haveValidSnapshotNamedWithTolerance(@"unique snapshot name", 0.01);
```
### Sane defaults
`EXPMatchers+FBSnapshotTest` will automatically figure out the tests folder, and [add a reference image](https://github.com/dblock/ios-snapshot-test-case-expecta/blob/master/EXPMatchers%2BFBSnapshotTest.m#L84-L85) directory, if you'd like to override this, you should include a `beforeAll` block setting the `setGlobalReferenceImageDir` in each file containing tests.
```
beforeAll(^{
setGlobalReferenceImageDir(FB_REFERENCE_IMAGE_DIR);
});
```
### Example
A complete project can be found in [FBSnapshotTestCaseDemo](FBSnapshotTestCaseDemo).
Notably, take a look at [FBSnapshotTestCaseDemoSpecs.m](FBSnapshotTestCaseDemo/FBSnapshotTestCaseDemoTests/FBSnapshotTestCaseDemoSpecs.m) for a complete example, which is an expanded Specta version version of [FBSnapshotTestCaseDemoTests.m](https://github.com/facebook/ios-snapshot-test-case/blob/master/FBSnapshotTestCaseDemo/FBSnapshotTestCaseDemoTests/FBSnapshotTestCaseDemoTests.m).
Finally you can consult the tests for [ARTiledImageView](https://github.com/dblock/ARTiledImageView/tree/master/IntegrationTests) or [NAMapKit](https://github.com/neilang/NAMapKit/tree/master/Demo/DemoTests).
### License
MIT, see [LICENSE](LICENSE.md)

View File

@@ -0,0 +1,25 @@
//
// EXPRuntimeMatcher.h
// Expecta
//
// Created by Luke Redpath on 26/03/2012.
// Copyright (c) 2012 Peter Jihoon Kim. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "EXPMatcher.h"
#import "EXPDefines.h"
@interface EXPBlockDefinedMatcher : NSObject <EXPMatcher> {
EXPBoolBlock prerequisiteBlock;
EXPBoolBlock matchBlock;
EXPStringBlock failureMessageForToBlock;
EXPStringBlock failureMessageForNotToBlock;
}
@property(nonatomic, copy) EXPBoolBlock prerequisiteBlock;
@property(nonatomic, copy) EXPBoolBlock matchBlock;
@property(nonatomic, copy) EXPStringBlock failureMessageForToBlock;
@property(nonatomic, copy) EXPStringBlock failureMessageForNotToBlock;
@end

View File

@@ -0,0 +1,60 @@
//
// EXPRuntimeMatcher.m
// Expecta
//
// Created by Luke Redpath on 26/03/2012.
// Copyright (c) 2012 Peter Jihoon Kim. All rights reserved.
//
#import "EXPBlockDefinedMatcher.h"
@implementation EXPBlockDefinedMatcher
- (void)dealloc
{
self.prerequisiteBlock = nil;
self.matchBlock = nil;
self.failureMessageForToBlock = nil;
self.failureMessageForNotToBlock = nil;
[super dealloc];
}
@synthesize prerequisiteBlock;
@synthesize matchBlock;
@synthesize failureMessageForToBlock;
@synthesize failureMessageForNotToBlock;
- (BOOL)meetsPrerequesiteFor:(id)actual
{
if (self.prerequisiteBlock) {
return self.prerequisiteBlock();
}
return YES;
}
- (BOOL)matches:(id)actual
{
if (self.matchBlock) {
return self.matchBlock();
}
return YES;
}
- (NSString *)failureMessageForTo:(id)actual
{
if (self.failureMessageForToBlock) {
return self.failureMessageForToBlock();
}
return nil;
}
- (NSString *)failureMessageForNotTo:(id)actual
{
if (self.failureMessageForNotToBlock) {
return self.failureMessageForNotToBlock();
}
return nil;
}
@end

View File

@@ -0,0 +1,17 @@
//
// EXPDefines.h
// Expecta
//
// Created by Luke Redpath on 26/03/2012.
// Copyright (c) 2012 Peter Jihoon Kim. All rights reserved.
//
#ifndef Expecta_EXPDefines_h
#define Expecta_EXPDefines_h
typedef void (^EXPBasicBlock)(void);
typedef id (^EXPIdBlock)(void);
typedef BOOL (^EXPBoolBlock)(void);
typedef NSString *(^EXPStringBlock)(void);
#endif

View File

@@ -0,0 +1,13 @@
#import <Foundation/Foundation.h>
@interface EXPDoubleTuple : NSObject {
double *_values;
size_t _size;
}
@property (nonatomic, assign) double *values;
@property (nonatomic, assign) size_t size;
- (instancetype)initWithDoubleValues:(double *)values size:(size_t)size NS_DESIGNATED_INITIALIZER;
@end

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