diff --git a/Cupcake-Demo/Cupcake-Demo.xcodeproj/project.pbxproj b/Cupcake-Demo/Cupcake-Demo.xcodeproj/project.pbxproj new file mode 100644 index 0000000..ede404f --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo.xcodeproj/project.pbxproj @@ -0,0 +1,420 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 841B2C721E7BC5900084B37C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841B2C711E7BC5900084B37C /* AppDelegate.swift */; }; + 841B2C741E7BC5900084B37C /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841B2C731E7BC5900084B37C /* ViewController.swift */; }; + 841B2C771E7BC5900084B37C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 841B2C751E7BC5900084B37C /* Main.storyboard */; }; + 841B2C791E7BC5900084B37C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 841B2C781E7BC5900084B37C /* Assets.xcassets */; }; + 841B2C7C1E7BC5900084B37C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 841B2C7A1E7BC5900084B37C /* LaunchScreen.storyboard */; }; + 8429690F1EE900EC0060C61D /* __Privates__Implementation__.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842968FD1EE900EC0060C61D /* __Privates__Implementation__.swift */; }; + 842969101EE900EC0060C61D /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842968FE1EE900EC0060C61D /* Alert.swift */; }; + 842969111EE900EC0060C61D /* AttStr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842968FF1EE900EC0060C61D /* AttStr.swift */; }; + 842969121EE900EC0060C61D /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842969001EE900EC0060C61D /* Button.swift */; }; + 842969131EE900EC0060C61D /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842969011EE900EC0060C61D /* Color.swift */; }; + 842969141EE900EC0060C61D /* Cons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842969021EE900EC0060C61D /* Cons.swift */; }; + 842969151EE900EC0060C61D /* CPKStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842969031EE900EC0060C61D /* CPKStackView.swift */; }; + 842969161EE900EC0060C61D /* Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842969041EE900EC0060C61D /* Font.swift */; }; + 842969171EE900EC0060C61D /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842969051EE900EC0060C61D /* ImageView.swift */; }; + 842969181EE900EC0060C61D /* Img.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842969061EE900EC0060C61D /* Img.swift */; }; + 842969191EE900EC0060C61D /* Label.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842969071EE900EC0060C61D /* Label.swift */; }; + 8429691A1EE900EC0060C61D /* Stack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842969081EE900EC0060C61D /* Stack.swift */; }; + 8429691B1EE900EC0060C61D /* StaticTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842969091EE900EC0060C61D /* StaticTable.swift */; }; + 8429691C1EE900EC0060C61D /* Str.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8429690A1EE900EC0060C61D /* Str.swift */; }; + 8429691D1EE900EC0060C61D /* Styles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8429690B1EE900EC0060C61D /* Styles.swift */; }; + 8429691E1EE900EC0060C61D /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8429690C1EE900EC0060C61D /* TextField.swift */; }; + 8429691F1EE900EC0060C61D /* TextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8429690D1EE900EC0060C61D /* TextView.swift */; }; + 842969201EE900EC0060C61D /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8429690E1EE900EC0060C61D /* View.swift */; }; + 842ADB421EB9C53D0031B56C /* ExamplesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842ADB411EB9C53D0031B56C /* ExamplesViewController.swift */; }; + 84B599B31EB1FF56005ADDFB /* BasicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B599B21EB1FF56005ADDFB /* BasicViewController.swift */; }; + 84B599B51EB1FF6C005ADDFB /* EnhancementViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B599B41EB1FF6C005ADDFB /* EnhancementViewController.swift */; }; + 84B599B71EB32A9C005ADDFB /* StackViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B599B61EB32A9C005ADDFB /* StackViewController.swift */; }; + 84B599B91EB32CE3005ADDFB /* appList.plist in Resources */ = {isa = PBXBuildFile; fileRef = 84B599B81EB32CE3005ADDFB /* appList.plist */; }; + 84B599BB1EB334EA005ADDFB /* StaticViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B599BA1EB334EA005ADDFB /* StaticViewController.swift */; }; + 84CC7DF81EC94EC300817678 /* AppStoreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CC7DF71EC94EC300817678 /* AppStoreViewController.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 841B2C6E1E7BC5900084B37C /* Cupcake-Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Cupcake-Demo.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 841B2C711E7BC5900084B37C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 841B2C731E7BC5900084B37C /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 841B2C761E7BC5900084B37C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 841B2C781E7BC5900084B37C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 841B2C7B1E7BC5900084B37C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 841B2C7D1E7BC5900084B37C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 842968FD1EE900EC0060C61D /* __Privates__Implementation__.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = __Privates__Implementation__.swift; sourceTree = ""; }; + 842968FE1EE900EC0060C61D /* Alert.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = ""; }; + 842968FF1EE900EC0060C61D /* AttStr.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttStr.swift; sourceTree = ""; }; + 842969001EE900EC0060C61D /* Button.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; + 842969011EE900EC0060C61D /* Color.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = ""; }; + 842969021EE900EC0060C61D /* Cons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cons.swift; sourceTree = ""; }; + 842969031EE900EC0060C61D /* CPKStackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CPKStackView.swift; sourceTree = ""; }; + 842969041EE900EC0060C61D /* Font.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = ""; }; + 842969051EE900EC0060C61D /* ImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageView.swift; sourceTree = ""; }; + 842969061EE900EC0060C61D /* Img.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Img.swift; sourceTree = ""; }; + 842969071EE900EC0060C61D /* Label.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Label.swift; sourceTree = ""; }; + 842969081EE900EC0060C61D /* Stack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Stack.swift; sourceTree = ""; }; + 842969091EE900EC0060C61D /* StaticTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticTable.swift; sourceTree = ""; }; + 8429690A1EE900EC0060C61D /* Str.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Str.swift; sourceTree = ""; }; + 8429690B1EE900EC0060C61D /* Styles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Styles.swift; sourceTree = ""; }; + 8429690C1EE900EC0060C61D /* TextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = ""; }; + 8429690D1EE900EC0060C61D /* TextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextView.swift; sourceTree = ""; }; + 8429690E1EE900EC0060C61D /* View.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = ""; }; + 842ADB411EB9C53D0031B56C /* ExamplesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExamplesViewController.swift; sourceTree = ""; }; + 84B599B21EB1FF56005ADDFB /* BasicViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasicViewController.swift; sourceTree = ""; }; + 84B599B41EB1FF6C005ADDFB /* EnhancementViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnhancementViewController.swift; sourceTree = ""; }; + 84B599B61EB32A9C005ADDFB /* StackViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackViewController.swift; sourceTree = ""; }; + 84B599B81EB32CE3005ADDFB /* appList.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = appList.plist; sourceTree = ""; }; + 84B599BA1EB334EA005ADDFB /* StaticViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticViewController.swift; sourceTree = ""; }; + 84CC7DF71EC94EC300817678 /* AppStoreViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStoreViewController.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 841B2C6B1E7BC5900084B37C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 841B2C651E7BC5900084B37C = { + isa = PBXGroup; + children = ( + 841B2C701E7BC5900084B37C /* Cupcake-Demo */, + 841B2C6F1E7BC5900084B37C /* Products */, + ); + sourceTree = ""; + }; + 841B2C6F1E7BC5900084B37C /* Products */ = { + isa = PBXGroup; + children = ( + 841B2C6E1E7BC5900084B37C /* Cupcake-Demo.app */, + ); + name = Products; + sourceTree = ""; + }; + 841B2C701E7BC5900084B37C /* Cupcake-Demo */ = { + isa = PBXGroup; + children = ( + 842968FC1EE900EC0060C61D /* Cupcake */, + 841B2C711E7BC5900084B37C /* AppDelegate.swift */, + 841B2C731E7BC5900084B37C /* ViewController.swift */, + 84B599B21EB1FF56005ADDFB /* BasicViewController.swift */, + 84B599B41EB1FF6C005ADDFB /* EnhancementViewController.swift */, + 84B599B61EB32A9C005ADDFB /* StackViewController.swift */, + 84B599BA1EB334EA005ADDFB /* StaticViewController.swift */, + 842ADB411EB9C53D0031B56C /* ExamplesViewController.swift */, + 84CC7DF71EC94EC300817678 /* AppStoreViewController.swift */, + 841B2C751E7BC5900084B37C /* Main.storyboard */, + 841B2C781E7BC5900084B37C /* Assets.xcassets */, + 841B2C7A1E7BC5900084B37C /* LaunchScreen.storyboard */, + 84B599B81EB32CE3005ADDFB /* appList.plist */, + 841B2C7D1E7BC5900084B37C /* Info.plist */, + ); + path = "Cupcake-Demo"; + sourceTree = ""; + }; + 842968FC1EE900EC0060C61D /* Cupcake */ = { + isa = PBXGroup; + children = ( + 8429690A1EE900EC0060C61D /* Str.swift */, + 842968FF1EE900EC0060C61D /* AttStr.swift */, + 842969041EE900EC0060C61D /* Font.swift */, + 842969061EE900EC0060C61D /* Img.swift */, + 842969011EE900EC0060C61D /* Color.swift */, + 8429690E1EE900EC0060C61D /* View.swift */, + 842969071EE900EC0060C61D /* Label.swift */, + 842969051EE900EC0060C61D /* ImageView.swift */, + 842969001EE900EC0060C61D /* Button.swift */, + 8429690C1EE900EC0060C61D /* TextField.swift */, + 8429690D1EE900EC0060C61D /* TextView.swift */, + 842969091EE900EC0060C61D /* StaticTable.swift */, + 842968FE1EE900EC0060C61D /* Alert.swift */, + 842969081EE900EC0060C61D /* Stack.swift */, + 842969021EE900EC0060C61D /* Cons.swift */, + 8429690B1EE900EC0060C61D /* Styles.swift */, + 842969031EE900EC0060C61D /* CPKStackView.swift */, + 842968FD1EE900EC0060C61D /* __Privates__Implementation__.swift */, + ); + name = Cupcake; + path = ../../Cupcake; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 841B2C6D1E7BC5900084B37C /* Cupcake-Demo */ = { + isa = PBXNativeTarget; + buildConfigurationList = 841B2C801E7BC5900084B37C /* Build configuration list for PBXNativeTarget "Cupcake-Demo" */; + buildPhases = ( + 841B2C6A1E7BC5900084B37C /* Sources */, + 841B2C6B1E7BC5900084B37C /* Frameworks */, + 841B2C6C1E7BC5900084B37C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Cupcake-Demo"; + productName = "Cupcake-Demo"; + productReference = 841B2C6E1E7BC5900084B37C /* Cupcake-Demo.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 841B2C661E7BC5900084B37C /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0820; + LastUpgradeCheck = 0820; + ORGANIZATIONNAME = nerdycat; + TargetAttributes = { + 841B2C6D1E7BC5900084B37C = { + CreatedOnToolsVersion = 8.2.1; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 841B2C691E7BC5900084B37C /* Build configuration list for PBXProject "Cupcake-Demo" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 841B2C651E7BC5900084B37C; + productRefGroup = 841B2C6F1E7BC5900084B37C /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 841B2C6D1E7BC5900084B37C /* Cupcake-Demo */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 841B2C6C1E7BC5900084B37C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 84B599B91EB32CE3005ADDFB /* appList.plist in Resources */, + 841B2C7C1E7BC5900084B37C /* LaunchScreen.storyboard in Resources */, + 841B2C791E7BC5900084B37C /* Assets.xcassets in Resources */, + 841B2C771E7BC5900084B37C /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 841B2C6A1E7BC5900084B37C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 842969201EE900EC0060C61D /* View.swift in Sources */, + 842969141EE900EC0060C61D /* Cons.swift in Sources */, + 84B599B51EB1FF6C005ADDFB /* EnhancementViewController.swift in Sources */, + 84B599B31EB1FF56005ADDFB /* BasicViewController.swift in Sources */, + 8429691E1EE900EC0060C61D /* TextField.swift in Sources */, + 8429691C1EE900EC0060C61D /* Str.swift in Sources */, + 842969171EE900EC0060C61D /* ImageView.swift in Sources */, + 8429691B1EE900EC0060C61D /* StaticTable.swift in Sources */, + 841B2C741E7BC5900084B37C /* ViewController.swift in Sources */, + 842ADB421EB9C53D0031B56C /* ExamplesViewController.swift in Sources */, + 8429691D1EE900EC0060C61D /* Styles.swift in Sources */, + 842969151EE900EC0060C61D /* CPKStackView.swift in Sources */, + 842969131EE900EC0060C61D /* Color.swift in Sources */, + 84CC7DF81EC94EC300817678 /* AppStoreViewController.swift in Sources */, + 842969191EE900EC0060C61D /* Label.swift in Sources */, + 8429691F1EE900EC0060C61D /* TextView.swift in Sources */, + 841B2C721E7BC5900084B37C /* AppDelegate.swift in Sources */, + 84B599B71EB32A9C005ADDFB /* StackViewController.swift in Sources */, + 842969121EE900EC0060C61D /* Button.swift in Sources */, + 842969111EE900EC0060C61D /* AttStr.swift in Sources */, + 842969161EE900EC0060C61D /* Font.swift in Sources */, + 8429690F1EE900EC0060C61D /* __Privates__Implementation__.swift in Sources */, + 8429691A1EE900EC0060C61D /* Stack.swift in Sources */, + 842969181EE900EC0060C61D /* Img.swift in Sources */, + 842969101EE900EC0060C61D /* Alert.swift in Sources */, + 84B599BB1EB334EA005ADDFB /* StaticViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 841B2C751E7BC5900084B37C /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 841B2C761E7BC5900084B37C /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 841B2C7A1E7BC5900084B37C /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 841B2C7B1E7BC5900084B37C /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 841B2C7E1E7BC5900084B37C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + 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_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_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + 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 = 10.2; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 841B2C7F1E7BC5900084B37C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + 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_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_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + 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 = 10.2; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 841B2C811E7BC5900084B37C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = "Cupcake-Demo/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "nerdycat.Cupcake-Demo"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Debug; + }; + 841B2C821E7BC5900084B37C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = "Cupcake-Demo/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "nerdycat.Cupcake-Demo"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 841B2C691E7BC5900084B37C /* Build configuration list for PBXProject "Cupcake-Demo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 841B2C7E1E7BC5900084B37C /* Debug */, + 841B2C7F1E7BC5900084B37C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 841B2C801E7BC5900084B37C /* Build configuration list for PBXNativeTarget "Cupcake-Demo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 841B2C811E7BC5900084B37C /* Debug */, + 841B2C821E7BC5900084B37C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 841B2C661E7BC5900084B37C /* Project object */; +} diff --git a/Cupcake-Demo/Cupcake-Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Cupcake-Demo/Cupcake-Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..4c44220 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Cupcake-Demo/Cupcake-Demo.xcodeproj/project.xcworkspace/xcuserdata/admin.xcuserdatad/UserInterfaceState.xcuserstate b/Cupcake-Demo/Cupcake-Demo.xcodeproj/project.xcworkspace/xcuserdata/admin.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..d314c1c Binary files /dev/null and b/Cupcake-Demo/Cupcake-Demo.xcodeproj/project.xcworkspace/xcuserdata/admin.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Cupcake-Demo/Cupcake-Demo.xcodeproj/xcuserdata/admin.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Cupcake-Demo/Cupcake-Demo.xcodeproj/xcuserdata/admin.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..fe2b454 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo.xcodeproj/xcuserdata/admin.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,5 @@ + + + diff --git a/Cupcake-Demo/Cupcake-Demo.xcodeproj/xcuserdata/admin.xcuserdatad/xcschemes/Cupcake-Demo.xcscheme b/Cupcake-Demo/Cupcake-Demo.xcodeproj/xcuserdata/admin.xcuserdatad/xcschemes/Cupcake-Demo.xcscheme new file mode 100644 index 0000000..9ea478e --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo.xcodeproj/xcuserdata/admin.xcuserdatad/xcschemes/Cupcake-Demo.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Cupcake-Demo/Cupcake-Demo.xcodeproj/xcuserdata/admin.xcuserdatad/xcschemes/xcschememanagement.plist b/Cupcake-Demo/Cupcake-Demo.xcodeproj/xcuserdata/admin.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..fc6bf52 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo.xcodeproj/xcuserdata/admin.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,22 @@ + + + + + SchemeUserState + + Cupcake-Demo.xcscheme + + orderHint + 0 + + + SuppressBuildableAutocreation + + 841B2C6D1E7BC5900084B37C + + primary + + + + + diff --git a/Cupcake-Demo/Cupcake-Demo/AppDelegate.swift b/Cupcake-Demo/Cupcake-Demo/AppDelegate.swift new file mode 100644 index 0000000..06c85a2 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/AppDelegate.swift @@ -0,0 +1,44 @@ +// +// AppDelegate.swift +// Cupcake-Demo +// +// Created by nerdycat on 2017/3/17. +// Copyright © 2017 nerdycat. All rights reserved. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + func applicationWillResignActive(_ application: UIApplication) { + // 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 invalidate graphics rendering callbacks. Games should use this method to pause the game. + } + + func applicationDidEnterBackground(_ application: UIApplication) { + // 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. + } + + func applicationWillEnterForeground(_ application: UIApplication) { + // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. + } + + func applicationDidBecomeActive(_ application: UIApplication) { + // 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. + } + + func applicationWillTerminate(_ application: UIApplication) { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. + } +} + diff --git a/Cupcake-Demo/Cupcake-Demo/AppStoreViewController.swift b/Cupcake-Demo/Cupcake-Demo/AppStoreViewController.swift new file mode 100644 index 0000000..2348c75 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/AppStoreViewController.swift @@ -0,0 +1,100 @@ +// +// AppStoreViewController.swift +// Cupcake-Demo +// +// Created by nerdycat on 2017/5/15. +// Copyright © 2017 nerdycat. All rights reserved. +// + +import UIKit + +class AppStoreCell: UITableViewCell { + var iconView: UIImageView! + var actionButton: UIButton! + var indexLabel, titleLabel, categoryLabel: UILabel! + var ratingLabel, countLabel, iapLabel: UILabel! + + func update(app: Dictionary, index: Int) { + indexLabel.str(index + 1) + iconView.img(app["iconName"] as! String) + titleLabel.text = app["title"] as? String + categoryLabel.text = app["category"] as? String + countLabel.text = Str("(%@)", app["commentCount"] as! NSNumber) + iapLabel.isHidden = !(app["iap"] as! Bool) + + let rating = (app["rating"] as! NSNumber).intValue + var result = "" + for i in 0..<5 { result = result + (i < rating ? "★" : "☆") } + ratingLabel.text = result + + let price = app["price"] as! String + actionButton.str( price.characters.count > 0 ? "$" + price : "GET") + } + + func setupUI() { + indexLabel = Label.font(17).color("darkGray").align(.center).pin(.w(44)) + iconView = ImageView.pin(64, 64).radius(10).border(1.0 / UIScreen.main.scale, "#CCC") + + titleLabel = Label.font(15).lines(2) + categoryLabel = Label.font(13).color("darkGray") + + ratingLabel = Label.font(11).color("orange") + countLabel = Label.font(11).color("darkGray") + + actionButton = Button.font("15").color("#0065F7").border(1, "#0065F7").radius(3) + actionButton.highColor("white").highBg("#0065F7").padding(5, 10) + + iapLabel = Label.font(9).color("darkGray").lines(2).str("In-App\nPurchases").align(.center) + + let ratingStack = HStack(ratingLabel, countLabel).gap(5) + let midStack = VStack(titleLabel, categoryLabel, ratingStack).gap(4) + let actionStack = VStack(actionButton, iapLabel).gap(4).align(.center) + + HStack(indexLabel, iconView, 10, midStack, "<-->", 10, actionStack).embedIn(self.contentView, 10, 0, 10, 15) + } + + override init(style: UITableViewCellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setupUI() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + + +class AppStoreViewController: UITableViewController { + + var appList: Array>! + + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return UITableViewAutomaticDimension + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return appList.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! AppStoreCell + let app = self.appList[indexPath.row] + cell.update(app: app, index: indexPath.row) + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + } + + override func viewDidLoad() { + super.viewDidLoad() + + self.tableView.estimatedRowHeight = 84 + self.tableView.register(AppStoreCell.self, forCellReuseIdentifier: "cell") + + let path = Bundle.main.path(forResource: "appList", ofType: "plist") + appList = NSArray(contentsOfFile: path!) as? Array> + for _ in 1..<5 { appList.append(contentsOf: appList) } + } +} diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppIcon.appiconset/Contents.json b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..b8236c6 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,48 @@ +{ + "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" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/Contents.json b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/bitmoji.imageset/Bitmoji@2x.jpeg b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/bitmoji.imageset/Bitmoji@2x.jpeg new file mode 100644 index 0000000..17e5e78 Binary files /dev/null and b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/bitmoji.imageset/Bitmoji@2x.jpeg differ diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/bitmoji.imageset/Contents.json b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/bitmoji.imageset/Contents.json new file mode 100644 index 0000000..8117a99 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/bitmoji.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Bitmoji@2x.jpeg", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/fivenights.imageset/Contents.json b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/fivenights.imageset/Contents.json new file mode 100644 index 0000000..c775aa8 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/fivenights.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "fivehights@2x.jpeg", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/fivenights.imageset/fivehights@2x.jpeg b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/fivenights.imageset/fivehights@2x.jpeg new file mode 100644 index 0000000..0ba0a17 Binary files /dev/null and b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/fivenights.imageset/fivehights@2x.jpeg differ diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/mario.imageset/Contents.json b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/mario.imageset/Contents.json new file mode 100644 index 0000000..2a3d688 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/mario.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "mario@2x.jpeg", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/mario.imageset/mario@2x.jpeg b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/mario.imageset/mario@2x.jpeg new file mode 100644 index 0000000..ef526c2 Binary files /dev/null and b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/mario.imageset/mario@2x.jpeg differ diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/messenger.imageset/Contents.json b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/messenger.imageset/Contents.json new file mode 100644 index 0000000..438fdbe --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/messenger.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "messenger@2x.jpeg", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/messenger.imageset/messenger@2x.jpeg b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/messenger.imageset/messenger@2x.jpeg new file mode 100644 index 0000000..4cee193 Binary files /dev/null and b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/messenger.imageset/messenger@2x.jpeg differ diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/minecraft.imageset/Contents.json b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/minecraft.imageset/Contents.json new file mode 100644 index 0000000..7bc6778 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/minecraft.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "minecraft@2x.jpeg", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/minecraft.imageset/minecraft@2x.jpeg b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/minecraft.imageset/minecraft@2x.jpeg new file mode 100644 index 0000000..2586c93 Binary files /dev/null and b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/minecraft.imageset/minecraft@2x.jpeg differ diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/snapchat.imageset/Contents.json b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/snapchat.imageset/Contents.json new file mode 100644 index 0000000..3bce919 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/snapchat.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "snapchat@2x.jpeg", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/snapchat.imageset/snapchat@2x.jpeg b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/snapchat.imageset/snapchat@2x.jpeg new file mode 100644 index 0000000..eb69888 Binary files /dev/null and b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/snapchat.imageset/snapchat@2x.jpeg differ diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/youtube.imageset/Contents.json b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/youtube.imageset/Contents.json new file mode 100644 index 0000000..dd88e83 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/youtube.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "youtube@2x.jpeg", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/youtube.imageset/youtube@2x.jpeg b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/youtube.imageset/youtube@2x.jpeg new file mode 100644 index 0000000..00e8f0c Binary files /dev/null and b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/AppStore/youtube.imageset/youtube@2x.jpeg differ diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Contents.json b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/Contents.json b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/customers.imageset/Contents.json b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/customers.imageset/Contents.json new file mode 100644 index 0000000..30047e7 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/customers.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "customers.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/customers.imageset/customers.png b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/customers.imageset/customers.png new file mode 100644 index 0000000..5f9e584 Binary files /dev/null and b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/customers.imageset/customers.png differ diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/dashboard.imageset/Contents.json b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/dashboard.imageset/Contents.json new file mode 100644 index 0000000..fc1819a --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/dashboard.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "dashboard.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/dashboard.imageset/dashboard.png b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/dashboard.imageset/dashboard.png new file mode 100644 index 0000000..af05b1a Binary files /dev/null and b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/dashboard.imageset/dashboard.png differ diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/entities.imageset/Contents.json b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/entities.imageset/Contents.json new file mode 100644 index 0000000..62bc7fc --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/entities.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "entities.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/entities.imageset/entities.png b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/entities.imageset/entities.png new file mode 100644 index 0000000..b289811 Binary files /dev/null and b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/entities.imageset/entities.png differ diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/jobsites.imageset/Contents.json b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/jobsites.imageset/Contents.json new file mode 100644 index 0000000..8f4a8df --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/jobsites.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "jobsites.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/jobsites.imageset/jobsites.png b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/jobsites.imageset/jobsites.png new file mode 100644 index 0000000..e6e0648 Binary files /dev/null and b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/jobsites.imageset/jobsites.png differ diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/requests.imageset/Contents.json b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/requests.imageset/Contents.json new file mode 100644 index 0000000..d3f62e1 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/requests.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "requests.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/requests.imageset/requests.png b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/requests.imageset/requests.png new file mode 100644 index 0000000..6527c7e Binary files /dev/null and b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/requests.imageset/requests.png differ diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/shubox.imageset/Contents.json b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/shubox.imageset/Contents.json new file mode 100644 index 0000000..ee77309 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/shubox.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "shubox.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/shubox.imageset/shubox.png b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/shubox.imageset/shubox.png new file mode 100644 index 0000000..f1d1897 Binary files /dev/null and b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Examples/shubox.imageset/shubox.png differ diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/Contents.json b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/airplane.imageset/Contents.json b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/airplane.imageset/Contents.json new file mode 100644 index 0000000..ca09572 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/airplane.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "airplane.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/airplane.imageset/airplane.png b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/airplane.imageset/airplane.png new file mode 100644 index 0000000..f05a1a5 Binary files /dev/null and b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/airplane.imageset/airplane.png differ diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/display.imageset/Contents.json b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/display.imageset/Contents.json new file mode 100644 index 0000000..c20c203 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/display.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "display.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/display.imageset/display.png b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/display.imageset/display.png new file mode 100644 index 0000000..3adcb73 Binary files /dev/null and b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/display.imageset/display.png differ diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/disturb.imageset/Contents.json b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/disturb.imageset/Contents.json new file mode 100644 index 0000000..f76f483 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/disturb.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "disturb.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/disturb.imageset/disturb.png b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/disturb.imageset/disturb.png new file mode 100644 index 0000000..a279475 Binary files /dev/null and b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/disturb.imageset/disturb.png differ diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/general.imageset/Contents.json b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/general.imageset/Contents.json new file mode 100644 index 0000000..e972a3b --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/general.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "general.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/general.imageset/general.png b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/general.imageset/general.png new file mode 100644 index 0000000..3f027c4 Binary files /dev/null and b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/general.imageset/general.png differ diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/wlan.imageset/Contents.json b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/wlan.imageset/Contents.json new file mode 100644 index 0000000..4dbdf2f --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/wlan.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "wlan.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/wlan.imageset/wlan.png b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/wlan.imageset/wlan.png new file mode 100644 index 0000000..75fa53e Binary files /dev/null and b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/Settings/wlan.imageset/wlan.png differ diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/arrow.imageset/Contents.json b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/arrow.imageset/Contents.json new file mode 100644 index 0000000..4b678ae --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/arrow.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "arrow@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/arrow.imageset/arrow@2x.png b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/arrow.imageset/arrow@2x.png new file mode 100644 index 0000000..85bb646 Binary files /dev/null and b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/arrow.imageset/arrow@2x.png differ diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/candle.imageset/Contents.json b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/candle.imageset/Contents.json new file mode 100644 index 0000000..4d079ae --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/candle.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "candle@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/candle.imageset/candle@2x.png b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/candle.imageset/candle@2x.png new file mode 100644 index 0000000..3e8e138 Binary files /dev/null and b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/candle.imageset/candle@2x.png differ diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/cat.imageset/Contents.json b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/cat.imageset/Contents.json new file mode 100644 index 0000000..4064081 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/cat.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "cat.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/cat.imageset/cat.png b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/cat.imageset/cat.png new file mode 100644 index 0000000..8a46781 Binary files /dev/null and b/Cupcake-Demo/Cupcake-Demo/Assets.xcassets/cat.imageset/cat.png differ diff --git a/Cupcake-Demo/Cupcake-Demo/Base.lproj/LaunchScreen.storyboard b/Cupcake-Demo/Cupcake-Demo/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..fdf3f97 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Cupcake-Demo/Cupcake-Demo/Base.lproj/Main.storyboard b/Cupcake-Demo/Cupcake-Demo/Base.lproj/Main.storyboard new file mode 100644 index 0000000..53d73e0 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/Base.lproj/Main.storyboard @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Cupcake-Demo/Cupcake-Demo/BasicViewController.swift b/Cupcake-Demo/Cupcake-Demo/BasicViewController.swift new file mode 100644 index 0000000..1e60506 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/BasicViewController.swift @@ -0,0 +1,60 @@ +// +// BasicViewController.swift +// Cupcake-Demo +// +// Created by nerdycat on 2017/4/27. +// Copyright © 2017 nerdycat. All rights reserved. +// + +import UIKit + +class BasicViewController: BaseViewController { + + override func setupUI() { + + //View + let box = View.bg("red").pin(20, 20, 50, 50).border(4).onClick({ _ in + print("box") + }) + + let circle = View.bg("blue").makeCons({ + $0.left.top.equal(box).right.top.offset(20, 0) + $0.size.equal(50, 50) + }).radius(-1).shadow(0.7) + + + //Label + let label = Label.str("This is a normal Label.").font(17).color("66,66,66").pin(.xy(20, 100)) + + var att = AttStr("This is an attributed Label.").font(17).color("#3A3A3A") + att.select("attributed Label").underline().select(.range(8, 2)).color("red") + + let attLabel = Label.str(att).pin(.xy(20, 130)) + + + //ImageView + let candle = ImageView.img("candle").pin(.xy(20, 180)) + let tintedCandle = ImageView.img("$candle").tint("#86BD5B").pin(.xy(70, 180)) + + + //Button + let done = Button.str("Done").padding(5, 10).bg("red").highBg("blue").pin(.xy(20, 260)).radius(6) + + att = AttStr("Friends\n1024").font(13).select(.number).font("17") + + let friends = Button.str(att).border(1).highBg("darkGray,0.2").makeCons({ + $0.left.centerY.equal(done).right.centerY.offset(20, 0) + }).padding(10).lines() + + let more = Button.str("More").img("arrow").color("black").gap(5).reversed().makeCons({ make in + make.left.centerY.equal(friends).right.centerY.offset(20, 0) + }).onClick({ _ in + Alert.title("Alert").message("You just clicked the button.").action("OK").show() + }).touchInsets(-20) + + self.view.addSubviews(box, circle, label, attLabel, candle, tintedCandle, done, friends, more) + } +} + + + diff --git a/Cupcake-Demo/Cupcake-Demo/EnhancementViewController.swift b/Cupcake-Demo/Cupcake-Demo/EnhancementViewController.swift new file mode 100644 index 0000000..5a416b5 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/EnhancementViewController.swift @@ -0,0 +1,67 @@ +// +// EnhancementViewController.swift +// Cupcake-Demo +// +// Created by nerdycat on 2017/4/27. +// Copyright © 2017 nerdycat. All rights reserved. +// + +import UIKit + +class EnhancementViewController: BaseViewController { + + override func setupUI() { + + //Label with lineSpacing and link + let str = "With #Cupcake, @Label now support lineSpacing and Link handing. Additionally, @TextField and @TextView both have the abilities to set max length and placeholder. " + + let attStr = AttStr(str).select("Link handing", .hashTag, .nameTag).link() + + let label = Label.str(attStr).lineGap(10).lines().onLink({ text in + print(text) + }).embedIn(self.view, 15, 15, 15) + + + //TextField with maxLength + let nameField = TextField.pin(40).padding(0, 8).border(1).maxLength(5).hint("normal") + let codeField = TextField.pin(40).padding(0, 8).border(1).maxLength(4).hint("secure").keyboard(.numberPad).secure() + + nameField.makeCons({ make in + make.top.equal(label).bottom.offset(20) + make.left.offset(15) + + }).onFinish({ _ in + codeField.becomeFirstResponder() + }) + + codeField.makeCons({ make in + make.top.equal(nameField) + make.left.equal(nameField).right.offset(10) + make.right.offset(-15) + make.width.equal(nameField) + + }).onChange({ [unowned self] codeField in + if codeField.text?.characters.count == 4 { + self.view.viewWithTag(101)?.becomeFirstResponder() + } + }) + + + //TextView with placeholder and maxLength + let textView = TextView.padding(8).maxLength(40).border(1).makeCons({ make in + make.left.right.offset(15, -15) + make.top.equal(nameField).bottom.offset(10) + make.height.equal(100) + }).hint("comment") + + textView.tag = 101 + self.view.addSubviews(nameField, codeField, textView) + + self.view.onClick({ view in + view.endEditing(true) + }) + } +} + + + diff --git a/Cupcake-Demo/Cupcake-Demo/ExamplesViewController.swift b/Cupcake-Demo/Cupcake-Demo/ExamplesViewController.swift new file mode 100644 index 0000000..0a6eeb2 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/ExamplesViewController.swift @@ -0,0 +1,183 @@ +// +// ExamplesViewController.swift +// Cupcake-Demo +// +// Created by nerdycat on 2017/5/3. +// Copyright © 2017 nerdycat. All rights reserved. +// + +import UIKit + + +class ExamplesViewController: BaseViewController { + + override func setupUI() { + let titles = ["Signup", "Shubox", "Dashboard", "AppStore"] + + PlainTable(titles).embedIn(self.view).onClick({ [unowned self] row in + var target: UIViewController! + + switch row.indexPath.row { + case 0: target = SignupViewController() + case 1: target = ShuboxViewController() + case 2: target = DashboardViewController() + case 3: target = AppStoreViewController() + default: break + } + + target.title = row.cell.textLabel?.text + self.push(target) + }) + } +} + + + +class SignupViewController: BaseViewController { + + override func setupUI() { + //https://dribbble.com/shots/3346069-001-Log-in-Sing-up + + self.view.bg("#184367") + + let iphone5 = UIScreen.main.bounds.width == 320 + let card = View.bg("white").radius(4).embedIn(self.view, iphone5 ? 40 : 60, 30) + + let inputStyle = Styles.pin(40, .lowHugging).font(14) + Styles("ruler").bg("#C7C7CD").pin(1) + + let name = Label.str("FULL NAME").font(17) + let nameField = TextField.hint("Enter your full name").maxLength(15).styles(inputStyle) + let line1 = View.styles("ruler") + + let email = Label.str("E-MAIL").font(17) + let emailField = TextField.hint("Your E-mail goes here").keyboard(.emailAddress).styles(inputStyle) + let line2 = View.styles("ruler") + + let pw = Label.str("Password").font(17) + let pwField = TextField.hint("Enter your password").maxLength(10).secure().styles(inputStyle) + let line3 = View.styles("ruler") + + let statement = Label.str("☑️ I agree all statements in").color("lightGray").font(12) + let term = Button.str( AttStr("Terms of service").color("#8DD6E5").font("12").underline() ).margin(0, -22) + + let login = Button.str("LOG IN").font(15).bg("#4A96E3").pin(44, .lowHugging).radius(4) + + VStack(name, nameField, line1, 30, email, emailField, line2, 30, + pw, pwField, line3, 30, statement , term, "<-->", login).embedIn(card, iphone5 ? 30: 50, 30, 30, 30) + + + Button.str("X").font(13).color("#D2E0E8").bg("#EDF2F5").radius(-1).pin(16, 16, .maxX(-10), .y(10)).addTo(card) + Button.str("HELP").font(13).padding(10).pin(.centerX(0), .maxY(-5)).addTo(self.view) + } + + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + self.view.endEditing(true) + } +} + + + +class ShuboxViewController: BaseViewController { + + override func setupUI() { + //https://dribbble.com/shots/3462307-Responsive-Shubox + + let logo = Label.str("Shubox").color("#FC6560").font("30") + + let navStyle = Styles.color("darkGray").highColor("red").font(15) + Styles("btn").color("#FC6560").highColor("white").highBg("#FC6560").font("15").padding(12, 30).border(3, "#FC6560").radius(-1) + + let pricing = Button.str("Pricing").styles(navStyle) + let docs = Button.str("Docs").styles(navStyle) + let demos = Button.str("Demos").styles(navStyle) + let blog = Button.str("Blog").styles(navStyle) + let signIn = Button.str("Sign In").styles(navStyle).color("#FC6560") + + let nav = HStack(pricing, docs, demos, blog, signIn).gap(15) + + let simpleFast = Label.str("Simple. Fast. \nCustomizable.").color("#7C60CE").font("30").lines().align(.center) + let upload = Label.str("Upload images from your web app directly to Amazon S3.").color("#BE9FDE").font(15).lines().align(.center) + + let startTrial = Button.str("Start Your Free Trial").styles("btn") + let image = ImageView.img("shubox").pin(.ratio) + + let items: [Any] = [logo, 15, nav, 45, simpleFast, 15, upload, 30, startTrial, "<-->", image] + VStack(items).align(.center).embedIn(self.view, 10, 15, 0, 15) + } +} + + + +class DashboardViewController: BaseViewController { + + class DashButton: UIButton { + let subtitle: String! + + override func setTitle(_ title: String?, for state: UIControlState) { + if state == .normal { + let att = AttStr( + AttStr(title).font("18").color("#181D42"), "\n", + AttStr(subtitle).font(11).color("darkGray") + ).lineGap(3).align(.center) + self.str(att) + } else { + super.setTitle(title, for: state) + } + } + + init(_ subtitle: String) { + self.subtitle = subtitle + super.init(frame: CGRect.zero) + self.pin(.lowHugging, 80).lines().gap(15).highBg("lightGray,0.2") + self.adjustsImageWhenHighlighted = false + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + } + + override func setupUI() { + //https://dribbble.com/shots/3441336-Mobile-dashboard-items-list-light-design + + let logo = ImageView.img("dashboard") + let profile = Button.img("customers") + let header = HStack(logo, "<-->", profile) + + let welcome = Label.str("Welcome back,\nAndrew").font(18).color("darkGray").lines(2) + + let number = Label.str(83).font("AvenirNext-Bold,90").color("#181D42") + let dash = View.pin(12, 3).bg("#DE2F43").radius(-1) + let jobsites = Label.str("Jobsites\nrequests").font(13).color("darkGray").lines(2) + let requests = HStack(number, 20, VStack(dash, 3, jobsites)).align(.baseline) + + let jobBtn = DashButton("Jobsites").img("jobsites").str(346) + let requestsBtn = DashButton("Requests").img("requests").str(83) + let customersBtn = DashButton("Customers").img("customers").str(12) + + let entitiesBtn = DashButton("Entities").img("entities").str(22).makeCons({ + $0.width.equal(jobBtn) + $0.width.equal(requestsBtn) + $0.width.equal(customersBtn) + }) + + let tiles = View.margin(0, 10) + VStack(HStack(jobBtn, requestsBtn), HStack(customersBtn, entitiesBtn)).embedIn(tiles) + + VStack(header, 30, welcome, 15, requests, 40, tiles).embedIn(self.view, 30, 30, 30) + + let horLine = View.bg("lightGray,0.4").pin(1).makeCons({ + $0.left.bottom.equal(jobBtn) + $0.width.equal(jobBtn).multiply(2) + }) + + let verLine = View.bg("lightGray,0.4").pin(.w(1)).makeCons({ + $0.top.right.equal(jobBtn) + $0.height.equal(jobBtn).multiply(2) + }) + + self.view.addSubviews(horLine, verLine) + } +} + diff --git a/Cupcake-Demo/Cupcake-Demo/Info.plist b/Cupcake-Demo/Cupcake-Demo/Info.plist new file mode 100644 index 0000000..93b96dc --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + + diff --git a/Cupcake-Demo/Cupcake-Demo/StackViewController.swift b/Cupcake-Demo/Cupcake-Demo/StackViewController.swift new file mode 100644 index 0000000..ecc77cf --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/StackViewController.swift @@ -0,0 +1,63 @@ +// +// StackViewController.swift +// Cupcake-Demo +// +// Created by nerdycat on 2017/4/28. +// Copyright © 2017 nerdycat. All rights reserved. +// + +import UIKit + +class StackViewController: BaseViewController { + + var stack: CPKStackView! + + func randomView() -> UIView { + let width = CGFloat(arc4random_uniform(30) + 20) + let height = CGFloat(arc4random_uniform(30) + 20) + let view = View.bg("random").pin(.wh(width, height)).onClick({ + $0.removeFromSuperview() + }) + return view + } + + func setupButtons() { + let s = Styles.padding(3, 5).bg("red").font(15) + + let addView = Button.styles(s).str("add").onClick({[unowned self] _ in + self.stack.addArrangedSubview(item: self.randomView()) + }) + + let attachGap = Button.styles(s).str("attachGap").onClick({[unowned self] _ in + self.stack.addArrangedSubview(item: arc4random_uniform(30)) + }) + + let gap = Button.styles(s).str("gap").onClick({[unowned self] _ in + self.stack.spacing = CGFloat(arc4random_uniform(30)) + }) + + let align = Button.styles(s).str("align").onClick({[unowned self] _ in + var next = self.stack.alignment.rawValue + 1 + if next > 2 { next = 0 } + let alignment = CPKStackAlignment(rawValue: next)! + self.stack.alignment = alignment + }) + + let axis = Button.styles(s).str("axis").onClick({[unowned self] _ in + self.stack.axis = self.stack.axis == .vertical ? .horizontal : .vertical + }) + + HStack("<-->", addView, attachGap, gap, align, axis, "<-->").gap(8).embedIn(self.view, nil, 0, 0, 0) + } + + override func setupUI() { + super.setupUI() + + Label.str("click view to remove").font(15).color("lightGray").align(.center).embedIn(self.view, 10, 0, 0) + stack = VStack(randomView()).border(1).pin(.center).addTo(self.view) + + setupButtons() + } +} + + diff --git a/Cupcake-Demo/Cupcake-Demo/StaticViewController.swift b/Cupcake-Demo/Cupcake-Demo/StaticViewController.swift new file mode 100644 index 0000000..e96e0c7 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/StaticViewController.swift @@ -0,0 +1,282 @@ +// +// StaticViewController.swift +// Cupcake-Demo +// +// Created by nerdycat on 2017/4/28. +// Copyright © 2017 nerdycat. All rights reserved. +// + +import UIKit + +class StaticViewController: BaseViewController { + + override func setupUI() { + weak var weakSelf = self + + GroupTable( + Section( + Row.img("airplane").str("Airplane Mode").switchOn(false).onChange({ row in + print(row.switchView.isOn) + }), + Row.img("wlan").str("WLAN").detail("Not connected").arrow().onClick({ _ in + weakSelf?.push(WLANViewController()) + }), + Row.img("disturb").str("Do Not Disturb").arrow().onClick({ _ in + weakSelf?.push(DisturbViewController()) + }) + ), + + Section( + Row.img("general").str("General").arrow().custom({ row in + let badge = Button.pin(22, 22, .maxX(-5), .centerY(0)).radius(-1).str("1").font(14).bg("red") + badge.isUserInteractionEnabled = false + row.cell.contentView.addSubview(badge) + }).onClick({ _ in + weakSelf?.push(GeneralViewController()) + }), + Row.img("display").str("Display & Brightness").arrow().onClick({ _ in + + }).onClick({ _ in + weakSelf?.push(DisplayViewController()) + }) + ) + + ).embedIn(self.view) + } +} + + +class WLANViewController: BaseViewController { + override func setupUI() { + let footer = "Known networks will be joined automatically. If no known networks are available, you will have to manually select a network." + + GroupTable( + Section( + Row.str("WLAN").switchOn().onChange({ row in + print(row.switchView.isOn) + }) + ), + + Section( + Row.str("Wireless 1").detail("\u{0001F512} \u{268C}").accessory(.detailButton).onButton({ _ in + Alert.title("Wireless 1").message("detail button tapped").action("OK").show() + }).onClick({ _ in + print("Wireless 1") + }), + + Row.str("Wireless 2").detail("\u{0001F513} \u{2630}").accessory(.detailButton).onButton({ _ in + Alert.title("Wireless 2").message("detail button tapped").action("OK", { + print("OK") + }).cancel("Cancel").show() + }).onClick({ _ in + print("Wireless 2") + }) + ).header("CHOOSE A NETWORK..."), + + Section( + Row.str("Ask to Join Networks").switchOn(false).onChange({ row in + print(row.switchView.isOn) + }) + ).footer(footer) + ).embedIn(self.view) + } +} + + +class DisturbViewController: BaseViewController { + override func setupUI() { + + let footer1 = "When Do Not Disturb is enabled calls and alerts that arrive while locked will be silenced, and a moon icon will appear in the status bar." + let footer2 = "Incoming calls and notifications will be silenced while iPhone is either locked or unlocked." + + GroupTable( + Section( + Row.str("Manual").switchOn(false).onChange({ row in + print(row.switchView.isOn) + }) + ).footer(footer1), + + Section( + Row.str("Always").check().onClick({ _ in + print("always") + }), + Row.str("Only while iPhone is locked").onClick({ _ in + print("only locked") + }) + ).singleCheck().header("SILENCE:").footer(footer2) + + ).embedIn(self.view) + } +} + + +class GeneralViewController: BaseViewController { + var table: StaticTableView! + + override func setupUI() { + weak var weakSelf = self + + table = GroupTable( + Section( + Row.str("Name").arrow().onClick({ _ in + weakSelf?.push(NameViewController()) + }) + ), + Section( + Row.str("Software Update").arrow().onClick({ _ in + weakSelf?.push(UpdateViewController()) + }) + ) + ).embedIn(self.view) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + table.update(detail: NameViewController.name, at: IndexPath(row: 0, section: 0)) + } +} + + +class DisplayViewController: BaseViewController { + var autoLockRow: StaticRow! + + override func setupUI() { + autoLockRow = Row.str("Auto-Lock").detail("").arrow().onClick({ [unowned self] _ in + self.push(AutoLockViewController()) + }) + + GroupTable( + Section( + Row.custom({ row in + let slider = UISlider().embedIn(row.cell.contentView, 0, 15) + slider.minimumValueImage = Img("$candle").resize(0.5) + slider.maximumValueImage = Img("$candle").resize(0.7) + }) + ).header("BRIGHTNESS"), + + Section(autoLockRow) + ).embedIn(self.view) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + autoLockRow.detail(AutoLockViewController.selectedOption()) + } +} + + +class NameViewController: BaseViewController { + static var name = "My-iPhone" + var textField: UITextField! + + override func setupUI() { + let name = NameViewController.name + + GroupTable( + Row.custom({ row in + self.textField = TextField.str(name).hint(name).clearMode(.whileEditing).onFinish({ [unowned self] textField in + NameViewController.name = textField.text ?? "" + self.navigationController!.popViewController(animated: true) + }).embedIn(row.cell.contentView, 0, 15) + }) + ).embedIn(self.view) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + textField.becomeFirstResponder() + } +} + + +class UpdateViewController: BaseViewController { + override func setupUI() { + let desc = "iOS 10.3.1 introduces new features including the ability to locate AirPods using Find my iPhone and more ways to use Siri with payment, ride booking and automaker apps.\n\nFor information on the security content of Apple software update, please visit this website: https://support.apple.com/kb/HT201222" + + let attDesc = AttStr(desc).font(15).select(.url).link() + + GroupTable( + Section( + Row.custom({ row in + let icon = ImageView.pin(60, 60).img("general") + let title = Label.str("iOS 10.3.1").font("15") + let cops = Label.str("Apple Inc.").font(13) + let status = Label.str("Downloaded").font(13) + + let desc = Label.str(attDesc).lines().onLink({ text in + print(text) + }) + + VStack( + HStack( icon, VStack(title, cops, status).gap(2) ).gap(10), + 12, + desc + ).embedIn(row.cell.contentView, 10, 15) + }).height(-1), + + Row.str("Learn More").arrow().onClick({ _ in + print("learn more") + }) + ), + + Section( + Row.str(AttStr("Install Now").color("#157EFB")).onClick({ _ in + print("install") + }) + ) + ).embedIn(self.view) + } +} + + +class AutoLockViewController: BaseViewController { + static let options = ["30 Seconds", "1 Minute", "2 Minutes", "3 Minutes", "4 Minutes", "Never"] + static var optionIndex = 1 + + var tableView: StaticTableView! + + class func selectedOption() -> String { + return options[optionIndex] + } + + override func setupUI() { + tableView = GroupTable( + Section(AutoLockViewController.options).singleCheck() + ).custom({ row in + if row.indexPath.row == AutoLockViewController.optionIndex { + row.check() + } + }).embedIn(self.view) + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillAppear(animated) + AutoLockViewController.optionIndex = tableView.checkedIndexPaths.first!.row + } +} + + +class BaseViewController: UIViewController { + + func push(_ vc: UIViewController) { + self.navigationController?.pushViewController(vc, animated: true) + } + + func setupUI() { + + } + + override func viewDidLoad() { + super.viewDidLoad() + self.view.backgroundColor = .white + setupUI() + } +} + + + + + + + + diff --git a/Cupcake-Demo/Cupcake-Demo/ViewController.swift b/Cupcake-Demo/Cupcake-Demo/ViewController.swift new file mode 100644 index 0000000..53d5484 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/ViewController.swift @@ -0,0 +1,40 @@ +// +// ViewController.swift +// Cupcake-Demo +// +// Created by nerdycat on 2017/3/17. +// Copyright © 2017 nerdycat. All rights reserved. +// + +import UIKit + +class ViewController: BaseViewController { + + override func setupUI() { + self.title = "Cupcake" + + let titles = ["Basic", "Enhancement", "Stack", "StaticTable", "Examples"] + + PlainTable(titles).embedIn(self.view).onClick({ [unowned self] row in + var target: UIViewController! + + switch row.indexPath.row { + case 0: target = BasicViewController() + case 1: target = EnhancementViewController() + case 2: target = StackViewController() + case 3: target = StaticViewController() + case 4: target = ExamplesViewController() + default: break + } + + target.title = row.cell.textLabel?.text + self.push(target) + }) + } +} + + + + + + diff --git a/Cupcake-Demo/Cupcake-Demo/appList.plist b/Cupcake-Demo/Cupcake-Demo/appList.plist new file mode 100644 index 0000000..a4307e5 --- /dev/null +++ b/Cupcake-Demo/Cupcake-Demo/appList.plist @@ -0,0 +1,118 @@ + + + + + + iconName + mario.jpeg + title + Super Mario Run + category + Games + rating + 3 + commentCount + 6053 + price + + iap + + + + iconName + bitmoji.jpeg + title + Bitmoji - Your Personal Emoji + category + Utilities + rating + 4 + commentCount + 11 + price + + iap + + + + iconName + messenger.jpeg + title + Messenger + category + Social Netowrking + rating + 3 + commentCount + 2052 + price + + iap + + + + iconName + youtube.jpeg + title + YouTube - Watch and Share Videos, Music & Clips Support + category + Photo & Video + rating + 3.5 + commentCount + 38 + price + + iap + + + + iconName + snapchat.jpeg + title + Snapchat + category + Photo & Video + rating + 3 + commentCount + 33 + price + + iap + + + + iconName + fivenights.jpeg + title + Five Nights at Freddy's: Sister Location Support + category + Games + rating + 4.5 + commentCount + 814 + price + 2.99 + iap + + + + iconName + minecraft.jpeg + title + Minecraft: Pocket Edition Support + category + Games + rating + 4.5 + commentCount + 869 + price + 0.99 + iap + + + + diff --git a/Cupcake.podspec b/Cupcake.podspec new file mode 100644 index 0000000..864e558 --- /dev/null +++ b/Cupcake.podspec @@ -0,0 +1,21 @@ +Pod::Spec.new do |s| + + s.name = "Cupcake" + s.version = "0.1.0" + s.summary = "An easy way to create and layout UI components for iOS." + + s.description = <<-DESC + An easy way to create and layout UI components for iOS. Written in Swift. + DESC + + s.homepage = "https://github.com/nerdycat/Cupcake" + s.license = "MIT" + s.author = { "nerdycat" => "nerdymozart@gmail.com" } + + s.platform = :ios, "8.0" + s.source = { :git => "https://github.com/nerdycat/Cupcake.git", :tag => "#{s.version}" } + s.requires_arc = true + + s.source_files = "Cupcake/*.swift" + +end diff --git a/Cupcake/Alert.swift b/Cupcake/Alert.swift new file mode 100644 index 0000000..02e962b --- /dev/null +++ b/Cupcake/Alert.swift @@ -0,0 +1,120 @@ +// +// Alert.swift +// Cupcake +// +// Created by nerdycat on 2017/3/28. +// Copyright © 2017 nerdycat. All rights reserved. +// + +import UIKit + +/** + * An easy way to Create Alert and ActionSheet. + * Usages: + + Alert.title("Title").message("Message go here").cancel("Cancel").action("OK", { + print("OK") + }).show() + + ActionSheet.title("Title").message("Message go here").action("Action1", { + print("Action1") + }).action("Action2", { + print("Action2") + }).destructive("Delete", { + print("Delete") + }).cancel("Cancel").show() + */ + +public var Alert: AlertMaker { + return AlertMaker(style: .alert) +} + +public var ActionSheet: AlertMaker { + return AlertMaker(style: .actionSheet) +} + + +public extension AlertMaker { + + /** + * Setting Alert/ActionSheet title + * Usages: + .title("Title") + */ + @discardableResult public func title(_ title: Any) -> Self { + self.cpkTitle = title + return self + } + + /** + * Setting Alert/ActionSheet message + * Usages: + .message("Message go here") + */ + @discardableResult public func message(_ message: Any) -> Self { + self.cpkMessage = message + return self + } + + /** + * Setting tintColor + * tint use Color() internally, so it can take any kind of values that Color() supported. + * See Color.swift for more information. + * Usages: + .tint("red") + .tint("#00F") + ... + */ + @discardableResult public func tint(_ tint: Any) -> Self { + self.cpkTint = tint + return self + } + + /** + * Adding action button with title and a optional callback handler. + * You can have multiply action button at the same time. + * Usages: + .action("Option1") + .action("Option2", { /* do something */ }) + */ + @discardableResult public func action(_ title: Any, _ callback: (()->())? = nil) -> Self { + self.cpk_addAction(style: .default, title: title, handler: callback) + return self + } + + /** + * Adding cancel button with title and a optional callback handler. + * You can only have one cancel button. + * Usages: + .cancel("Cancel") + .cancel("Cancel", { /* do something */ } + */ + @discardableResult public func cancel(_ title: Any, _ callback: (()->())? = nil) -> Self { + self.cpk_addAction(style: .cancel, title: title, handler: callback) + return self + } + + /** + * Adding destructive button with title and a optional callback handler. + * You can have multiply action button at the same time. + * Usages: + .destructive("Delete") + .destructive("Delete", { /* do someting */ } + */ + @discardableResult public func destructive(_ title: Any, _ callback: (()->())? = nil) -> Self { + self.cpk_addAction(style: .destructive, title: title, handler: callback) + return self + } + + /** + * Present Alert/ActionSheet + * You must call this method in the end to make Alert/ActionSheet visible. + * Usages: + .show() //present in the top visible controller + .show(someController) //present in someController + */ + public func show(_ inside: UIViewController? = nil) { + self.cpk_present(inside) + } +} + diff --git a/Cupcake/AttStr.swift b/Cupcake/AttStr.swift new file mode 100644 index 0000000..3503003 --- /dev/null +++ b/Cupcake/AttStr.swift @@ -0,0 +1,359 @@ +// +// AttStr.swift +// Cupcake +// +// Created by nerdycat on 2017/3/17. +// Copyright © 2017 nerdycat. All rights reserved. +// + +import UIKit + +/** + * Create NSMutableAttributedString with String or UIImage. + * Usages: + AttStr("hello world").select("world").color("red") + AttStr("A big smile ", Img("smile"), " !!") //insert image attachment + */ +public func AttStr(_ objects: Any?...) -> NSMutableAttributedString { + let result = NSMutableAttributedString() + + for object in objects { + var subAtt: NSAttributedString? = nil + + if object is NSAttributedString { + subAtt = object as? NSAttributedString + + } else if object is String { + subAtt = NSAttributedString(string: object as! String) + + } else if object is UIImage { + let attachment = NSTextAttachment() + attachment.image = object as? UIImage + subAtt = NSAttributedString(attachment: attachment) + } + + if subAtt != nil { + result.append(subAtt!) + } + } + + result.cpk_select(range: NSMakeRange(0, result.length), setFlag: false) + return result +} + + +extension NSMutableAttributedString { + + /** + * NSFontAttributeName + * font use Font() internally, so it can take any kind of values that Font() supported. + * See Font.siwft for more information. + * Usages: + .font(15) + .font("20") + .font("body") + .font(someLabel.font) + ... + */ + @discardableResult public func font(_ style: Any) -> Self { + cpk_addAttribute(name: NSFontAttributeName, value: Font(style)) + return self + } + + /** + * NSForegroundColorAttributeName + * color use Color() internally, so it can take any kind of values that Color() supported. + * See Color.swift for more information. + * Usages: + .color(@"red") + .color(@"#F00") + .color(@"255,0,0") + .color(someView.backgroundColor) + ... + */ + @discardableResult public func color(_ any: Any) -> Self { + cpk_addAttribute(name: NSForegroundColorAttributeName, value: Color(any)!) + return self + } + + /** + * NSBackgroundColorAttributeName + * bg use Color() internally, so it can take any kind of values that Color() supported. + * See Color.swift for more information. + * Usages: + .bg(@"red") + .bg(@"#F00") + .bg(@"255,0,0") + .bg(someView.backgroundColor) + .bg("cat") //using image + ... + */ + @discardableResult public func bg(_ any: Any) -> Self { + cpk_addAttribute(name: NSBackgroundColorAttributeName, value: Color(any) ?? Color(Img(any))!) + return self + } + + /** + * NSUnderlineStyleAttributeName, NSUnderlineColorAttributeName + * underline's second argument use Color() internally, so it can take any kind of values that Color() supported. + * See Color.swift for more information. + * Usages: + .underline() //single underline with the same color of text + .underline(.patternDash) //dash underline with the same color of text + .underline("red") //single underline with red color + .underline(.styleDouble, "red") //double underline with red color + ... + */ + @discardableResult public func underline(_ style: NSUnderlineStyle = .styleSingle, _ color: Any? = nil) -> Self { + var styles = NSNumber(value: style.rawValue) + if style != .styleNone && style != .styleSingle && style != .styleThick && style != .styleDouble { + styles = NSNumber(value: style.rawValue | NSUnderlineStyle.styleSingle.rawValue) + } + + cpk_addAttribute(name: NSUnderlineStyleAttributeName, value: styles) + if let underlineColor = Color(color) { + cpk_addAttribute(name: NSUnderlineColorAttributeName, value: underlineColor) + } + return self + } + + @discardableResult public func underline(_ color: Any) -> Self { + return underline(.styleSingle, color) + } + + /** + * NSStrikethroughStyleAttributeName, NSStrikethroughColorAttributeName + * strikethrough's second argument use Color() internally, so it can take any kind of values that Color() supported. + * See Color.swift for more information. + * Usages: + .strikethrough() //single strikethrough with the same color of text + .strikethrough(.patternDash) //dash strikethrough with the same color of text + .strikethrough("red") //single strikethrough with red color + .strikethrough(.styleDouble, "red") //double strikethrough with red color + ... + */ + @discardableResult public func strikethrough(_ style: NSUnderlineStyle = .styleSingle, _ color: Any? = nil) -> Self { + var styles = NSNumber(value: style.rawValue) + if style != .styleNone && style != .styleSingle && style != .styleThick && style != .styleDouble { + styles = NSNumber(value: style.rawValue | NSUnderlineStyle.styleSingle.rawValue) + } + + cpk_addAttribute(name: NSStrikethroughStyleAttributeName, value: styles) + if let strikethroughColor = Color(color) { + cpk_addAttribute(name: NSStrikethroughColorAttributeName, value: strikethroughColor) + } + return self + } + + @discardableResult + public func strikethrough(_ color: Any) -> Self { + return strikethrough(.styleSingle, color) + } + + /** + * NSStrokeWidthAttributeName, NSStrokeColorAttributeName + * stroke's second argument use Color() internally, so it can take any kind of values that Color() supported. + * See Color.swift for more information. + * Usages: + .stroke(1) + .stroke(-4, "red") + */ + @discardableResult + public func stroke(_ width: CGFloat, _ color: Any? = nil) -> Self { + cpk_addAttribute(name: NSStrokeWidthAttributeName, value: width) + if let strokeColor = Color(color) { + cpk_addAttribute(name: NSStrokeColorAttributeName, value: strokeColor) + } + return self + } + + /** + * NSObliquenessAttributeName + * Usages: + .oblique(0.3) + .oblique(-0.3) + */ + @discardableResult + public func oblique(_ value: CGFloat) -> Self { + cpk_addAttribute(name: NSObliquenessAttributeName, value: value) + return self + } + + /** + * NSBaselineOffsetAttributeName + * Usages: + .offset(20) + .offset(-20) + */ + @discardableResult + public func offset(_ offset: CGFloat) -> Self { + cpk_addAttribute(name: NSBaselineOffsetAttributeName, value: offset) + return self + } + + /** + * NSLinkAttributeName + * Also can be used to add clickable link for UILabel. + * Usages: + .link("http://www.google.com") + .link() //mark as link for UILabel + */ + @discardableResult + public func link(_ url: String? = nil) -> Self { + if let urlString = url { + cpk_addAttribute(name: NSLinkAttributeName, value: urlString) + } else { + cpk_addAttribute(name: CPKLabelLinkAttributeName, value: CPKLabelLinkAttributeValue) + } + return self + } + + /** + * Line spacing + * Usages: + .lineGap(10) + */ + @discardableResult + public func lineGap(_ spacing: CGFloat) -> Self { + cpk_addParagraphAttribute(key: "lineSpacing", value: spacing) + return self + } + + /** + * First line head indent + * Usages: + .indent(20) + */ + @discardableResult + public func indent(_ headIntent: CGFloat) -> Self { + cpk_addParagraphAttribute(key: "firstLineHeadIndent", value: headIntent) + return self + } + + /** + * TextAlignment + * Usages: + .align(.center) + .align(.justified) + ... + */ + @discardableResult + public func align(_ alignment: NSTextAlignment) -> Self { + cpk_addParagraphAttribute(key: "alignment", value: NSNumber(value: alignment.rawValue)) + return self + } + + /** + * Select substrings + * By default Attributes are applied to the whole string. + You can make them only affect some parts of them by selecting substrings with regular expression or range. + + * You can pass multiply options at the same time. + * See AttStrSelectionOptions for more information. + + * Usages: + AttStr("hello world").select("world").color("red") //only "world" are red + AttStr("abc123").select("[a-z]+").color("red") //only "abc" are red + AttStr("abc123").select(.number).color("red") //only "123" are red + AttStr("@Tim at #Apple").select(.nameTag, .hashTag).color("red") //@Tim" and "#Apple" are red + AttStr("@Tim at #apple").select(.range(5, 2)).color("red") //only "@at" are red + ... + + * .select("pattern") is just the shorthand of .select(.match("pattern")) + */ + @discardableResult + public func select(_ optionOrStringLiterals: AttStrSelectionOptions...) -> Self { + + for option in optionOrStringLiterals { + var regExp: NSRegularExpression? + var patternString: String? + var selectedRange = false + + switch option { + case .all: + cpk_select(range: NSMakeRange(0, self.length)) + + case .url: + regExp = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) + + case .date: + regExp = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.date.rawValue) + + case .phoneNumber: + regExp = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.phoneNumber.rawValue) + + case .hashTag: + patternString = "(?= 0 ? location : self.length + location, length)) + return self + } + + if patternString != nil { + regExp = try? NSRegularExpression(pattern: patternString!, + options: NSRegularExpression.Options(rawValue: 0)) + } + + if regExp != nil { + let matches = regExp!.matches(in: self.string, + options: NSRegularExpression.MatchingOptions(rawValue: 0), + range: NSMakeRange(0, self.length)) + + for result in matches { + cpk_select(range: result.range) + selectedRange = true + } + } + + if !selectedRange { + cpk_select(range: nil) + } + } + + return self + } + + /** + * Prevent overriding attribute. + * By default, the attribute value applied later will override the previous one if they are the same attributes. + * Usages: + AttStr(@"hello").color(@"red").color(@"green") //green color + AttStr(@"hello").color(@"red").preventOverride.color(@"green") //red color + */ + @discardableResult + public func preventOverride(_ flag: Bool = true) -> Self { + self.cpkPreventOverrideAttribute = flag + return self + } +} + + +public enum AttStrSelectionOptions { + case all //select whole string + case url //select all urls + case date //select all dates + case number //select all numbers + case phoneNumber //select all phone numbers + case hashTag //select all hash tags + case nameTag //select all name tags + + case match(Any) //select substrings with regExp pattern or regExp Object + case range(Int, Int) //select substring with range +} + + + diff --git a/Cupcake/Button.swift b/Cupcake/Button.swift new file mode 100644 index 0000000..9db8a1e --- /dev/null +++ b/Cupcake/Button.swift @@ -0,0 +1,227 @@ +// +// Button.swift +// Cupcake +// +// Created by nerdycat on 2017/3/23. +// Copyright © 2017 nerdycat. All rights reserved. +// + +import UIKit + +public var Button: UIButton { + let button = UIButton() + cpk_higherHuggingAndResistance(forView: button) + return button +} + +public extension UIButton { + + /** + * Setting normal title or normal attributedTitle + * str can take any kind of value, even primitive type like Int. + * Usages: + .str(1024) + .str("hello world") + .str( AttStr("hello world").strikethrough() ) + ... + */ + @discardableResult public func str(_ any: Any) -> Self { + if let attStr = any as? NSAttributedString { + setAttributedTitle(attStr, for: .normal) + } else { + setTitle(String(describing: any), for: .normal) + } + return self + } + + /** + * Setting font + * font use Font() internally, so it can take any kind of values that Font() supported. + * See Font.siwft for more information. + * Usages: + .font(15) + .font("20") + .font("body") + .font("Helvetica,15") + .font(someLabel.font) + ... + **/ + @discardableResult public func font(_ any: Any) -> Self { + self.titleLabel?.font = Font(any) + return self + } + + /** + * Setting titleColor + * color use Color() internally, so it can take any kind of values that Color() supported. + * See Color.swift for more information. + * Usages: + .color(@"red") + .color(@"#F00") + .color(@"255,0,0") + .color(someLabel.textColor) + ... + */ + @discardableResult public func color(_ any: Any) -> Self { + setTitleColor(Color(any), for: .normal) + return self + } + + /** + * Setting highlighted titleColor + * highColor use Color() internally, so it can take any kind of values that Color() supported. + * See Color.swift for more information. + * Usages: + .highColor(@"red") + .highColor(@"#F00") + .highColor(@"255,0,0") + .highColor(someLabel.textColor) + ... + */ + @discardableResult public func highColor(_ any: Any) -> Self { + setTitleColor(Color(any), for: .highlighted) + return self + } + + /** + * Setting normal image + * img use Img() internally, so it can take any kind of values that Img() supported. + * See Img.swift for more information. + * Usages: + .img("cat") + .img("#button-background") + .img("$home-icon") + .img(someImage) + ... + */ + @discardableResult public func img(_ any: Any) -> Self { + let image = Img(any) + setImage(image, for: .normal) + if self.frame.isEmpty { + self.frame.size = image.size + } + return self + } + + /** + * Setting highlighted image + * highImg use Img() internally, so it can take any kind of values that Img() supported. + * See Img.swift for more information. + * Usages: + .highImg("cat") + .highImg("#button-background") + .highImg("$home-icon") + .highImg(someImage) + ... + */ + @discardableResult public func highImg(_ any: Any) -> Self { + setImage(Img(any), for: .highlighted) + return self + } + + /** + * Setting background with Color or Image. + * bg use Img() internally, so it can take any kind of values that Img() supported. + * See Img.swift for more information. + * Usages: + .bg(@"red") + .bg(@"#F00") + .bg(@"255,0,0") + .bg(someView.backgroundColor) + .bg("cat") //using image + .bg(someImage) //using image + ... + */ + @discardableResult override public func bg(_ any: Any) -> Self { + let image = Img(any) + setBackgroundImage(image, for: .normal) + cpk_masksToBoundsIfNeed() + + if self.frame.isEmpty { + self.frame.size = image.size + } + return self + } + + /** + * Setting highlighted background with Color or Image. + * highBg use Img() internally, so it can take any kind of values that Img() supported. + * See Img.swift for more information. + * Usages: + .highBg(@"red") + .highBg(@"#F00") + .highBg(@"255,0,0") + .highBg(someView.backgroundColor) + .highBg("cat") //using image + .highBg(someImage) //using image + ... + */ + @discardableResult public func highBg(_ any: Any) -> Self { + setBackgroundImage(Img(any), for: .highlighted) + cpk_masksToBoundsIfNeed() + return self + } + + /** + * Setting contentEdgeInsets + * Usages: + .padding(10) //top: 10, left: 10, bottom: 10, right: 10 + .padding(10, 20) //top: 10, left: 20, bottom: 10, right: 20 + .padding(10, 20, 30) //top: 10, left: 20, bottom: 0 , right: 30 + .padding(10, 20, 30, 40) //top: 10, left: 20, bottom: 30, right: 40 + */ + @discardableResult public func padding(_ contentEdgeInsets: CGFloat...) -> Self { + cpk_updatePadding(contentEdgeInsets, forView: self) + return self + } + + /** + * Setting spacing between title and image. + * Usages: + .gap(10) + */ + @discardableResult public func gap(_ spacing: CGFloat) -> Self { + self.cpkGap = spacing + let halfGap = spacing / 2 + + self.titleEdgeInsets = UIEdgeInsetsMake(0, halfGap, 0, -halfGap) + self.imageEdgeInsets = UIEdgeInsetsMake(0, -halfGap, 0, halfGap) + + var insets = self.cpkInsets ?? UIEdgeInsetsMake(0, 0, 0, 0) + insets.left += halfGap + insets.right += halfGap + self.contentEdgeInsets = insets + + return self + } + + /** + * Swapping title and image position. + * Usages: + .reversed() + .reversed(false) + */ + @discardableResult public func reversed(_ reversed: Bool = true) -> Self { + let t = reversed ? CATransform3DMakeScale(-1, 1, 1) : CATransform3DIdentity + self.layer.sublayerTransform = t + self.imageView?.layer.transform = t + self.titleLabel?.layer.transform = t + return self + } + + /** + * Enable multilines for Button. + * Usages: + .lines(2) + .lines(0) //multilines + .lines() //same as .lines(0) + */ + @discardableResult public func lines(_ numberOfLines: CGFloat = 0) -> Self { + self.titleLabel?.numberOfLines = Int(numberOfLines) + self.titleLabel?.lineBreakMode = .byWordWrapping + self.titleLabel?.textAlignment = .center + return self + } +} + + diff --git a/Cupcake/CPKStackView.swift b/Cupcake/CPKStackView.swift new file mode 100644 index 0000000..4818859 --- /dev/null +++ b/Cupcake/CPKStackView.swift @@ -0,0 +1,708 @@ +// +// StackView.swift +// Cupcake +// +// Created by nerdycat on 2017/3/29. +// Copyright © 2017 nerdycat. All rights reserved. +// + +import UIKit + + +/** + * Usually you don't use CPKStackView directly. + * Use HStack and VStack instread. See Stack.swift for more information. + */ + +fileprivate let kDefaultEnclosurePriority: UILayoutPriority = 200 +fileprivate let kAlignmentPriority: UILayoutPriority = 1000 +fileprivate let kSpacingPriority: UILayoutPriority = 1000 +fileprivate let kSpringPriority: UILayoutPriority = 1000 + +let kFixSizePriority: UILayoutPriority = 900 +let kLowPriority: UILayoutPriority = 100 +let kDefaultHuggingPriority: UILayoutPriority = 250 + + +public class CPKStackView: UIView { + + private var alignmentConstraints = [NSLayoutConstraint]() + private var spacingConstraints = [NSLayoutConstraint]() + private var enslosureConstraints = [NSLayoutConstraint]() + private var springConstraints = [NSLayoutConstraint]() + + private var _alignment: CPKStackAlignment = .left + private var _spacing: CGFloat = 0 + private var _axis: UILayoutConstraintAxis = .horizontal + + public private(set) var arrangedSubviews = [UIView]() + + + public var alignment: CPKStackAlignment { + get { return _alignment } + set { if _alignment != newValue { _alignment = newValue; alignmentDidChange() } } + } + + public var spacing: CGFloat { + get { return _spacing } + set { if _spacing != newValue { _spacing = newValue; spacingDidChange() } } + } + + public var axis: UILayoutConstraintAxis { + get { return _axis } + set { if _axis != newValue { _axis = newValue; axisDidChange() } } + } + + + public func addArrangedSubview(item: Any) { + insertArrangedSubview(item: item, at: self.arrangedSubviews.count) + } + + public func addArrangedSubviews(items: [Any]) { + for item in items { + if let array = item as? Array { + for item in array { + self.addArrangedSubview(item: item) + } + } else { + self.addArrangedSubview(item: item) + } + } + } + + public func insertArrangedSubview(item: Any, at index: Int) { + let sub = item is String ? StackSpring() : item + + if let view = sub as? UIView { + + self.insertSubview(view, at: index) + self.arrangedSubviews.insert(view, at: index) + + view.translatesAutoresizingMaskIntoConstraints = false + view.addObserver(self, forKeyPath: "hidden", options: [.new, .old], context: nil) + + if UIEdgeInsetsEqualToEdgeInsets(view.layoutMargins, UIEdgeInsetsMake(8, 8, 8, 8)) { + view.layoutMargins = UIEdgeInsets.zero + } + + if !view.isHidden { + addAndActivateConstraintsForView(at: index) + } + + } else if let array = sub as? [Any] { + for i in 0.. CGSize { + return systemLayoutSizeFitting(UILayoutFittingCompressedSize) + } + + + + //MARK: KVO + public override func observeValue(forKeyPath keyPath: String?, + of object: Any?, + change: [NSKeyValueChangeKey : Any]?, + context: UnsafeMutableRawPointer?) { + + if keyPath == "hidden" { + let oldValue = change?[.oldKey] as? Bool + let newValue = change?[.newKey] as? Bool + + if newValue != oldValue { + if let item = object as? UIView { + if let index = self.arrangedSubviews.index(of: item) { + + if item.isHidden { + removeAndDeactivateConstraintsForView(at: index) + } else { + addAndActivateConstraintsForView(at: index) + } + } + } + } + } + } + + + + //MARK: Constraints + private func addAndActivateConstraintsForView(at index: Int) { + if itemAt(index: index).isHidden { + return; + } + + var oldConstraints = [NSLayoutConstraint]() + var newConstraints = [NSLayoutConstraint]() + + let previousIndex = previousVisibleViewIndexForView(at: index) + let nextIndex = nextVisibleViewIndexForView(at: index) + + newConstraints.append(contentsOf: addAlignmentConstraint(at: index)) + newConstraints.append(contentsOf: addEnclosureConstraints(at: index)) + + if let c = removeSpacingConstraintBetween(index1: previousIndex, index2: nextIndex) { + oldConstraints.append(c) + } + + if let c = addSpacingConstraintBetween(index1: previousIndex, index2: index) { + newConstraints.append(c) + } + + if let c = addSpacingConstraintBetween(index1: index, index2: nextIndex) { + newConstraints.append(c) + } + + NSLayoutConstraint.deactivate(oldConstraints) + NSLayoutConstraint.activate(newConstraints) + + if let spring = itemAt(index: index) as? StackSpring { + spring.axis = self.axis + addAndActivateSpringConstraintsForSpring(at: index) + } + } + + private func addAndActivateConstraintsForAll() { + addAndActivateAlignmentConstraintsForAll() + addAndActivateSpacingConstraintsForAll() + addAndActivateEnclosureConstraintsForAll() + + for (i, item) in self.arrangedSubviews.enumerated() { + if let spring = item as? StackSpring { + spring.axis = self.axis + addAndActivateSpringConstraintsForSpring(at: i) + } + } + } + + private func removeAndDeactivateConstraintsForView(at index: Int) { + var oldConstraints = [NSLayoutConstraint]() + + if let c = removeAlignmentConstraint(at: index) { + oldConstraints.append(c) + } + + oldConstraints.append(contentsOf: removeEnclosureConstraints(at: index)) + NSLayoutConstraint.deactivate(oldConstraints) + + removeAndRebuildSpacingConstraints(at: index) + removeAndDeactivateSpringConstraintsForSpring(at: index) + } + + private func removeAndDeactivateAllConstriants() { + removeAndDeactivateAllAlignmentConstriants() + removeAndDeactivateAllEnclosureConstraints() + removeAndDeactivateAllSpacingConstraints() + removeAndDeactivateAllSpringsConstraints() + } + + + + //MARK: Alignment + @discardableResult + private func addAlignmentConstraint(at index: Int) -> [NSLayoutConstraint] { + var newConstraints = [NSLayoutConstraint]() + var att = NSLayoutAttribute.notAnAttribute + + if self.alignment == .fill { + + if self.axis == .vertical { + newConstraints.append( makeConstraint(index, .leftMargin, .equal, -1, .leftMargin, 1, 0, 1000) ) + newConstraints.append( makeConstraint(index, .rightMargin, .equal, -1, .rightMargin, 1, 0, 1000) ) + } else { + newConstraints.append( makeConstraint(index, .topMargin, .equal, -1, .topMargin, 1, 0, 1000) ) + newConstraints.append( makeConstraint(index, .bottomMargin, .equal, -1, .bottomMargin, 1, 0, 1000) ) + } + + } else { + if self.axis == .vertical { + if self.alignment == .left { att = .leftMargin } + if self.alignment == .right { att = .rightMargin } + if self.alignment == .center { att = .centerXWithinMargins } + + } else { + if self.alignment == .top { att = .topMargin } + if self.alignment == .bottom { att = .bottomMargin } + if self.alignment == .center { att = .centerYWithinMargins } + if self.alignment == .baseline { att = .lastBaseline } + } + + newConstraints.append( makeConstraint(index, att, .equal, -1, att, 1, 0, kAlignmentPriority) ) + } + + + self.alignmentConstraints.append(contentsOf: newConstraints) + return newConstraints + } + + private func addAndActivateAlignmentConstraintsForAll() { + for i in 0.. NSLayoutConstraint? { + let item = itemAt(index: index) + + if item != self { + for (i, c) in self.alignmentConstraints.enumerated() { + if c.firstItem === item || c.secondItem === item { + self.alignmentConstraints.remove(at: i) + return c + } + } + } + + return nil + } + + private func removeAndDeactivateAllAlignmentConstriants() { + NSLayoutConstraint.deactivate(self.alignmentConstraints) + self.alignmentConstraints.removeAll() + } + + + + //MARK: Enclosure + @discardableResult + private func addEnclosureConstraints(at index: Int) -> [NSLayoutConstraint] { + var newConstraints = [NSLayoutConstraint]() + + let enclosurePriority = kDefaultEnclosurePriority + + if self.axis == .vertical { + newConstraints.append(makeConstraint(index, .leftMargin, .equal, -1, .leftMargin, enclosurePriority)) + newConstraints.append(makeConstraint(index, .leftMargin, .greaterThanOrEqual, -1, .leftMargin, 1000)) + newConstraints.append(makeConstraint(index, .rightMargin, .equal, -1, .rightMargin, enclosurePriority)) + newConstraints.append(makeConstraint(index, .rightMargin, .lessThanOrEqual, -1, .rightMargin, 1000)) + + } else { + newConstraints.append(makeConstraint(index, .topMargin, .equal, -1, .topMargin, enclosurePriority)) + newConstraints.append(makeConstraint(index, .topMargin, .greaterThanOrEqual, -1, .topMargin, 1000)) + + var att: NSLayoutAttribute = .bottomMargin + if self.alignment == .baseline { + att = .lastBaseline + } + + newConstraints.append(makeConstraint(index, att, .equal, -1, att, enclosurePriority)) + newConstraints.append(makeConstraint(index, att, .lessThanOrEqual, -1, att, 1000)) + } + + self.enslosureConstraints.append(contentsOf: newConstraints) + return newConstraints + } + + private func addAndActivateEnclosureConstraintsForAll() { + for i in 0.. [NSLayoutConstraint] { + var oldConstriants = [NSLayoutConstraint]() + let item = itemAt(index: index) + + for i in stride(from: self.enslosureConstraints.count - 1, through: 0, by: -1) { + let c = self.enslosureConstraints[i] + if c.firstItem === item || c.secondItem === item { + oldConstriants.append(c) + self.enslosureConstraints.remove(at: i) + } + } + + return oldConstriants + } + + private func removeAndDeactivateAllEnclosureConstraints() { + NSLayoutConstraint.deactivate(self.enslosureConstraints) + self.enslosureConstraints.removeAll() + } + + + + //MARK: Spacing + @discardableResult + private func addSpacingConstraintBetween(index1: Int, index2: Int) -> NSLayoutConstraint? { + let item1 = itemAt(index: index1) + let item2 = itemAt(index: index2) + + if item1 === item2 { + return nil + } + + var att1 = NSLayoutAttribute.notAnAttribute + var att2 = NSLayoutAttribute.notAnAttribute + + if self.axis == .vertical { + att1 = (item1 == self ? .topMargin : .bottomMargin) + att2 = (item2 != self ? .topMargin : .bottomMargin) + } else { + att1 = (item1 == self ? .leftMargin : .rightMargin) + att2 = (item2 != self ? .leftMargin : .rightMargin) + } + + var spacing: CGFloat = 0 + + if let attachSpacing = item1.cpkAttachSpacing { + spacing = -attachSpacing + } else if item1 != self && item2 != self { + spacing = -self.spacing + } + + let c = makeConstraint(index1, att1, .equal, index2, att2, 1, spacing, kSpacingPriority) + self.spacingConstraints.append(c) + return c + } + + private func addAndActivateSpacingConstraintsForAll() { + if self.arrangedSubviews.count > 0 { + var index1 = -1 + + while index1 < self.arrangedSubviews.count { + let index2 = nextVisibleViewIndexForView(at: index1) + addSpacingConstraintBetween(index1: index1, index2: index2) + index1 = index2 + } + + NSLayoutConstraint.activate(self.spacingConstraints) + } + } + + private func removeSpacingConstraintBetween(index1: Int, index2: Int) -> NSLayoutConstraint? { + let item1 = itemAt(index: index1) + let item2 = itemAt(index: index2) + + for (i, c) in self.spacingConstraints.enumerated() { + if c.firstItem === item1 && c.secondItem === item2 { + self.spacingConstraints.remove(at: i) + return c + } + } + + return nil + } + + private func removeAndDeactivateAllSpacingConstraints() { + NSLayoutConstraint.deactivate(self.spacingConstraints) + self.spacingConstraints.removeAll() + } + + private func removeAndRebuildSpacingConstraints(at index: Int) { + var oldConstraints = [NSLayoutConstraint]() + var newConstarints = [NSLayoutConstraint]() + + let previousIndex = previousVisibleViewIndexForView(at: index) + let nextIndex = nextVisibleViewIndexForView(at: index) + + if let c = removeSpacingConstraintBetween(index1: previousIndex, index2: index) { + oldConstraints.append(c) + } + + if let c = removeSpacingConstraintBetween(index1: index, index2: nextIndex) { + oldConstraints.append(c) + } + + if oldConstraints.count > 0 { + if let c = addSpacingConstraintBetween(index1: previousIndex, index2: nextIndex) { + newConstarints.append(c) + } + } + + NSLayoutConstraint.deactivate(oldConstraints) + NSLayoutConstraint.activate(newConstarints) + } + + + private func attachSpaceDidChangeForView(at index: Int) { + let item1 = itemAt(index: index) + let item2 = itemAt(index: index + 1) + + if let spacing = item1.cpkAttachSpacing { + for c in self.spacingConstraints { + if c.firstItem === item1 && c.secondItem === item2 { + c.constant = -spacing + break + } + } + } + } + + + + //MARK: Spring + private func addAndActivateSpringConstraintsForSpring(at index: Int) { + var newConstraints = [NSLayoutConstraint]() + + for i in 0.. NSLayoutConstraint { + + let item1 = itemAt(index: index1) + let item2 = itemAt(index: index2) + + let c = NSLayoutConstraint(item: item1, + attribute: att1, + relatedBy: relation, + toItem: item2, + attribute: att2, + multiplier: multiplier, + constant: constant) + + c.priority = priority + return c + } + + private func makeConstraint(_ index1: Int, + _ att1: NSLayoutAttribute, + _ relation: NSLayoutRelation, + _ index2: Int, + _ att2: NSLayoutAttribute, + _ priority: UILayoutPriority) -> NSLayoutConstraint { + + return makeConstraint(index1, att1, relation, index2, att2, 1, 0, priority) + } + + private func previousVisibleViewIndexForView(at index: Int) -> Int { + for i in stride(from: index - 1, through: 0, by: -1) { + let item = itemAt(index: i) + if !item.isHidden { return i } + } + return -1 + } + + private func nextVisibleViewIndexForView(at index: Int) -> Int { + for i in stride(from: index + 1, to: self.arrangedSubviews.count, by: 1) { + let item = itemAt(index: i) + if !item.isHidden { return i } + } + return self.arrangedSubviews.count + } + + private func itemAt(index: Int) -> UIView { + if index >= 0 && index < self.arrangedSubviews.count { + return self.arrangedSubviews[index] + } else { + return self + } + } + + private func alignmentDidChange() { + removeAndDeactivateAllAlignmentConstriants() + removeAndDeactivateAllEnclosureConstraints() + + addAndActivateAlignmentConstraintsForAll() + addAndActivateEnclosureConstraintsForAll() + } + + private func spacingDidChange() { + for c in self.spacingConstraints { + let item1 = c.firstItem as! UIView + let item2 = c.secondItem as! UIView + + if item1 != self && item2 != self { + if let attachSpacing = item1.cpkAttachSpacing { + c.constant = -attachSpacing + } else { + c.constant = -self.spacing + } + } + } + } + + private func axisDidChange() { + removeAndDeactivateAllConstriants() + addAndActivateConstraintsForAll() + } +} + + + +extension UIView { + var cpkAttachSpacing: CGFloat? { + get { return cpk_associatedObjectFor(key: #function) as? CGFloat } + set { cpk_setAssociated(object: newValue, forKey: #function) } + } +} + + +class CPKTransformLayer: CATransformLayer { + override var isOpaque: Bool { + set {} + get { return true } + } +} + + +public class StackSpring: UIView { + private var _axis: UILayoutConstraintAxis = .horizontal + + fileprivate var axis: UILayoutConstraintAxis { + get { return _axis } + set { + if _axis != newValue { + _axis = newValue + updateContentPriorities() + invalidateIntrinsicContentSize() + } + } + } + + override init(frame: CGRect) { + super.init(frame: frame) + self.isUserInteractionEnabled = false + updateContentPriorities() + } + + public required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override var intrinsicContentSize: CGSize { + return CGSize.zero + } + + public override var backgroundColor: UIColor? { + set {} + get { return nil } + } + + public override var isOpaque: Bool { + set {} + get { return true } + } + + public override var clipsToBounds: Bool { + set {} + get { return false } + } + + public override class var layerClass: Swift.AnyClass { + return CPKTransformLayer.self + } + + private func updateContentPriorities() { + let horPriority = UILayoutPriority(self.axis == .horizontal ? 1 : 1000) + let verPriority = UILayoutPriority(self.axis == .horizontal ? 1000 : 1) + + setContentHuggingPriority(horPriority, for: .horizontal) + setContentHuggingPriority(verPriority, for: .vertical) + setContentCompressionResistancePriority(horPriority, for: .horizontal) + setContentCompressionResistancePriority(verPriority, for: .vertical) + } +} + + diff --git a/Cupcake/Color.swift b/Cupcake/Color.swift new file mode 100644 index 0000000..3f27bd7 --- /dev/null +++ b/Cupcake/Color.swift @@ -0,0 +1,106 @@ +// +// Color.swift +// Cupcake +// +// Created by nerdycat on 2017/3/17. +// Copyright © 2017 nerdycat. All rights reserved. +// + +import UIKit + +/** + * Create UIColor Object. + * Color argument can be: + 1) UIColor object + 2) UIImage object, return a pattern image color + 3) "red", "green", "blue", "clear", etc. (any system color) + 5) "random": randomColor + 6) "255,0,0": RGB color + 7) "#FF0000" or "0xF00": Hex color + + * All the string representation can have an optional alpha value. + + * Usages: + Color([UIColor redColor]) + Color("red") + Color("red,0.1") //with alpha + Color("255,0,0) + Color("255,0,0,1") //with alpha + Color("#FF0000") + Color("#F00,0.1") //with alpha + Color("random,0.5") + Color(Img("image")) //using image + ... + */ +func Color(_ any: Any?) -> UIColor? { + if any == nil { + return nil + } + + if let color = any as? UIColor { + return color; + } + + if let image = any as? UIImage { + return UIColor(patternImage: image) + } + + guard any is String else { + return nil + } + + var alpha: CGFloat = 1 + var components = (any as! String).components(separatedBy: ",") + + if components.count == 2 || components.count == 4 { + alpha = min(CGFloat(Float(components.last!) ?? 1), 1) + components.removeLast() + } + + var r: Int?, g: Int? , b: Int? + + if components.count == 1 { + let string = components.first! + let sel = NSSelectorFromString(string + "Color") + + //system color + if let color = UIColor.cpk_safePerform(selector: sel) as? UIColor { + return color.withAlphaComponent(alpha) + + } + + if string == "random" { + r = Int(arc4random_uniform(256)) + g = Int(arc4random_uniform(256)) + b = Int(arc4random_uniform(256)) + + } else if string.hasPrefix("#") { + if string.characters.count == 4 { + r = Int(string.subAt(1), radix:16)! * 17 + g = Int(string.subAt(2), radix:16)! * 17 + b = Int(string.subAt(3), radix:16)! * 17 + + } else if string.characters.count == 7 { + r = Int(string.subAt(1...2), radix:16) + g = Int(string.subAt(3...4), radix:16) + b = Int(string.subAt(5...6), radix:16) + } + } + + } else if components.count == 3 { + r = Int(components[0]) + g = Int(components[1]) + b = Int(components[2]) + } + + if r != nil && g != nil && b != nil { + return UIColor(red: CGFloat(r!) / 255.0, + green: CGFloat(g!) / 255.0, + blue: CGFloat(b!) / 255.0, + alpha: alpha) + } + + return nil +} + + diff --git a/Cupcake/Cons.swift b/Cupcake/Cons.swift new file mode 100644 index 0000000..49330e9 --- /dev/null +++ b/Cupcake/Cons.swift @@ -0,0 +1,230 @@ +// +// Constraint.swift +// Cupcake +// +// Created by nerdycat on 2017/3/22. +// Copyright © 2017 nerdycat. All rights reserved. +// + +import UIKit + +/** + * A SnapKit like syntax to setup constraints. + + * Usages: + view1.makeCons({ make in + make.left.top.equal(view2).offset(20, 20) + make.width.height.equal(100, 100) + }) + + view1.remakeCons({ + $0.left.equal(20) + $0.top.euqal(view2).bottom.offset(20) + $0.size.equal(view2).multiply(0.5) + }) + */ + +public extension ConsAtts { + + public var left: Cons { + return addAttributes(.left) + } + + public var leftMargin: Cons { + return addAttributes(.leftMargin) + } + + public var right: Cons { + return addAttributes(.right) + } + + public var rightMargin: Cons { + return addAttributes(.rightMargin) + } + + public var top: Cons { + return addAttributes(.top) + } + + public var topMargin: Cons { + return addAttributes(.topMargin) + } + + public var bottom: Cons { + return addAttributes(.bottom) + } + + public var bottomMargin: Cons { + return addAttributes(.bottomMargin) + } + + public var leading: Cons { + return addAttributes(.leading) + } + + public var leadingMargin: Cons { + return addAttributes(.leadingMargin) + } + + public var trailing: Cons { + return addAttributes(.trailing) + } + + public var trailingMagin: Cons { + return addAttributes(.trailingMargin) + } + + public var width: Cons { + return addAttributes(.width) + } + + public var height: Cons { + return addAttributes(.height) + } + + public var centerX: Cons { + return addAttributes(.centerX) + } + + public var centerY: Cons { + return addAttributes(.centerY) + } + + public var centerXInMargins: Cons { + return addAttributes(.centerXWithinMargins) + } + + public var centerYInMargins: Cons { + return addAttributes(.centerYWithinMargins) + } + + public var baseline: Cons { + return addAttributes(.lastBaseline) + } + + public var firstBaseline: Cons { + return addAttributes(.firstBaseline) + } + + public var center: Cons { + return addAttributes(.centerX, .centerY) + } + + public var origin: Cons { + return addAttributes(.left, .top) + } + + public var size: Cons { + return addAttributes(.width, .height) + } + + public var edge: Cons { + return addAttributes(.top, .left, .bottom, .right) + } +} + + +public extension Cons { + + /** + * equal relationship, can be UIView or values. This is the default relationship. + * If you don't specify item2 (by using equal, lessEqual or greaterEqual), + and the item1's attribute is not width and height, + then the item2 is automatically refer to item1's superview. + + * Usages: + make.left.equal(superview) + make.left.offset(0) //same as above + make.width.equal(100) + make.size.equal(100, 200) + */ + @discardableResult public func equal(_ item2OrValues: Any...) -> Self { + self.relation = .equal + updateSecondItem(item2OrValues) + return self + } + + /** + * lessThanOrEqual relationship, can be UIView or values. + * Usages: + make.left.lessEqual(superview) + make.width.lessEqual(100) + make.size.lessEqual(100, 200) + */ + @discardableResult public func lessEqual(_ item2OrValues: Any...) -> Self { + self.relation = .lessThanOrEqual + updateSecondItem(item2OrValues) + return self + } + + /** + * greaterThanOrEqual relationship, can be UIView or values. + * Usages: + make.left.greaterEqual(superview) + make.width.greaterEqual(100) + make.size.greaterEqual(100, 200) + */ + @discardableResult public func greaterEqual(_ item2OrValues: Any...) -> Self { + self.relation = .greaterThanOrEqual + updateSecondItem(item2OrValues) + return self + } + + /** + * offset can accept multiply values. + * Usages: + make.left.offset(20) + make.left.top.offset(20, 40) + make.edge.offset(10, 20, 30, 40) + ... + */ + @discardableResult public func offset(_ offsets: CGFloat...) -> Self { + self.constantValues = offsets + return self + } + + /** + * multiply can accept multiply values. + * Usages: + make.width.equal(view2).multiply(0.5) + make.size.equal(view2).multiply(0.5, 0.8) + ... + */ + @discardableResult public func multiply(_ multipliers: CGFloat...) -> Self { + self.multiplierValues = multipliers + return self + } + + /** + * priority can accept multiply values. + * Usages: + make.width.equal(100).priority(800) + make.edge.offset(10).priority(1000, 1000, 900, 900) + ... + */ + @discardableResult public func priority(_ priorities: UILayoutPriority...) -> Self { + self.priorityValues = priorities + return self + } + + /** + * Save constraints to variables, it can accept multiply values. + * Very useful when you need to alter constraint later. + * Usages: + var topConstriant = NSLayoutConstraint() + + View.bg("red").addTo(self.view).makeCons({ + $0.size.equal(100, 100) + $0.center.offset(0).priority(900) + $0.top.offset(0).saveTo(&topConstriant) + }) + ... + topConstraint.isActivate = false + */ + @discardableResult public func saveTo(_ constraints: UnsafeMutablePointer...) -> Self { + self.storePointers = constraints + return self + } +} + + diff --git a/Cupcake/Font.swift b/Cupcake/Font.swift new file mode 100644 index 0000000..7cb0e43 --- /dev/null +++ b/Cupcake/Font.swift @@ -0,0 +1,50 @@ +// +// Font.swift +// Cupcake +// +// Created by nerdycat on 2017/3/17. +// Copyright © 2017 nerdycat. All rights reserved. +// + +import UIKit + +/** + * Create UIFont object. + * Font argument can be: + 1) UIFont object + 2) 15: systemFontOfSize 15 + 3) "15": boldSystemFontOfSize 15 + 4) "headline", "body", "caption1", and any other UIFontTextStyle. + 5) "Helvetica,15": fontName + fontSize, separated by comma. + + * Usages: + Font(someLabel.font), + Font(15) + Font("15") + Font("body") + Font("Helvetica,15") + ... + */ +public func Font(_ any: Any) -> UIFont { + if let font = any as? UIFont { + return font + } + + if let string = any as? String { + let elements = string.components(separatedBy: ",") + if elements.count == 2 { + return UIFont(name: elements[0], size: CGFloat(Float(elements[1])!))! + } + + if let fontSize = Float(string), fontSize > 0 { + return UIFont.boldSystemFont(ofSize: CGFloat(fontSize)) + } + + let value = "UICTFontTextStyle" + string.capitalized + return UIFont.preferredFont(forTextStyle: UIFontTextStyle(rawValue: value)) + } + + return UIFont.systemFont(ofSize: CPKFloat(any)) +} + + diff --git a/Cupcake/ImageView.swift b/Cupcake/ImageView.swift new file mode 100644 index 0000000..adccf19 --- /dev/null +++ b/Cupcake/ImageView.swift @@ -0,0 +1,54 @@ +// +// ImageView.swift +// Cupcake +// +// Created by nerdycat on 2017/3/23. +// Copyright © 2017 nerdycat. All rights reserved. +// + +import UIKit + +public var ImageView: UIImageView { + let imageView = UIImageView() + cpk_higherHuggingAndResistance(forView: imageView) + return imageView +} + +public extension UIImageView { + + /** + * Setting image + * img use Img() internally, so it can take any kind of values that Img() supported. + * See Img.swift for more information. + * Usages: + .img("cat") + .img("#button-background") + .img("$home-icon") + .img(someImage) + ... + */ + @discardableResult public func img(_ any: Any) -> Self { + self.image = Img(any) + + if self.image != nil { + if self.frame.isEmpty { + self.frame.size = self.image!.size + } + } + + cpk_masksToBoundsIfNeed() + return self + } + + /** + * Setting contentMode + * Usages: + .mode(.scaleAspectFit) + .mode(.center) + ... + */ + @discardableResult public func mode(_ contentMode: UIViewContentMode) -> Self { + self.contentMode = contentMode + return self + } +} diff --git a/Cupcake/Img.swift b/Cupcake/Img.swift new file mode 100644 index 0000000..c0c8b59 --- /dev/null +++ b/Cupcake/Img.swift @@ -0,0 +1,123 @@ +// +// Img.swift +// Cupcake +// +// Created by nerdycat on 2017/3/17. +// Copyright © 2017 nerdycat. All rights reserved. +// + +import UIKit + +/** + * Create UIImage object. + * Img argument can be: + 1) UIImage object + 2) "imageName" + 3) "#imageName": stretchable image + 4) "$imageName": template image + 5) Any color value that Color() supported. + + * Prefixing image name with # character will create a stretchable image. + * Prefixing image name with $ character will create a template image. + * Passing a color value will create an 1x1 size image with specific color. + + * Usages: + Img(someImage) + Img("cat") + Img("#button-background") + Img("$home-icon") + Img("33,33,33,0.5") + Img("red").resize(100, 100) + ... + */ +public func Img(_ any: Any) -> UIImage { + + if let image = any as? UIImage { + return image + } + + if let string = any as? String { + let stretching = string.hasPrefix("#") + let templating = string.hasPrefix("$") + + let imageName = (stretching || templating) ? string.subFrom(1) : string + var image = UIImage(named: imageName) + + if stretching { + if image == nil { + image = UIImage(named: string) + } else { + let leftCap = Int(image!.size.width) / 2 + let topCap = Int(image!.size.height) / 2 + image = image?.stretchableImage(withLeftCapWidth:leftCap, topCapHeight: topCap) + } + } + + if templating { + if image == nil { + image = UIImage(named: string) + } else { + image = image?.withRenderingMode(.alwaysTemplate) + } + } + + if image != nil { + return image! + } + } + + if let color = Color(any) { + return cpk_onePointImageWithColor(color) + } else { + assert(false, "invalid image") + return UIImage() + } +} + + +public extension UIImage { + + /** + * Resize image. + * When passing integer value, it means the target size. + * When passing floating value, it means multiply with original size. + * Usages: + .resize(100, 100) //resize to 100x100 + .resize(100) //same as .resize(100, 100) + .resize(0.8, 0.8) //0.8 * original size + .resize(0.8) //same as .resize(0.8, 0.8) + */ + @discardableResult public func resize(_ p1: Any, _ p2: Any? = nil) -> UIImage { + var newWidth: CGFloat = 0 + var newHeight: CGFloat = 0 + + if let width = p1 as? Int { + newWidth = CGFloat(width) + } else { + newWidth = CPKFloat(p1) * self.size.width + } + + if p2 == nil { + newHeight = newWidth + + } else { + if let height = p2 as? Int { + newHeight = CGFloat(height) + } else { + newHeight = CPKFloat(p2!) * self.size.height + } + } + + let rect = CGRect(x: 0, y: 0, width: newWidth, height: newHeight) + let hasAlpha = cpk_imageHasAlphaChannel(self) + + UIGraphicsBeginImageContextWithOptions(rect.size, !hasAlpha, self.scale) + self.draw(in: rect) + let newImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + return newImage! + } +} + + diff --git a/Cupcake/Label.swift b/Cupcake/Label.swift new file mode 100644 index 0000000..6b06061 --- /dev/null +++ b/Cupcake/Label.swift @@ -0,0 +1,120 @@ +// +// Label.swift +// Cupcake +// +// Created by nerdycat on 2017/3/17. +// Copyright © 2017 nerdycat. All rights reserved. +// + +import UIKit + +public var Label: UILabel { + let label = UILabel() + return label +} + +public extension UILabel { + + /** + * Setting text or attributedText + * str can take any kind of value, even primitive type like Int. + * Usages: + .str(1024) + .str("hello world") + .str( AttStr("hello world").strikethrough() ) + ... + */ + @discardableResult public func str(_ any: Any) -> Self { + if let attStr = any as? NSAttributedString { + self.attributedText = attStr + } else { + self.text = String(describing: any) + } + return self + } + + /** + * Setting font + * font use Font() internally, so it can take any kind of values that Font() supported. + * See Font.swift for more information. + * Usages: + .font(15) + .font("20") + .font("body") + .font("Helvetica,15") + .font(someLabel.font) + ... + **/ + @discardableResult public func font(_ any: Any) -> Self { + self.font = Font(any) + return self + } + + /** + * Setting textColor + * color use Color() internally, so it can take any kind of values that Color() supported. + * See Color.swift for more information. + * Usages: + .color(@"red") + .color(@"#F00") + .color(@"255,0,0") + .color(someLabel.textColor) + ... + */ + @discardableResult public func color(_ any: Any) -> Self { + self.textColor = Color(any) + return self + } + + /** + * Setting numberOfLines + * Usages: + .lines(2) + .lines(0) //multilines + .lines() //same as .lines(0) + */ + @discardableResult public func lines(_ numberOfLines: CGFloat = 0) -> Self { + self.numberOfLines = Int(numberOfLines) + return self + } + + /** + * Setting lineSpacing + * Usages: + .lineGap(8) + */ + @discardableResult public func lineGap(_ lineSpacing: CGFloat) -> Self { + self.cpkLineGap = lineSpacing + return self + } + + /** + * Setting textAlignment + * Usages: + .align(.center) + .align(.justified) + ... + */ + @discardableResult public func align(_ textAlignment: NSTextAlignment) -> Self { + self.textAlignment = textAlignment + return self + } + + /** + * Setup link handler. + * Use onLink in conjunction with AttStr's link method to make text clickable. + * This will automatically set isUserInteractionEnabled to true as well. + * Be aware of retain cycle when using this method. + * Usages: + .onLink({ text in print(text) }) + .onLink({ [weak self] text in //capture self as weak reference when needed + print(text) + }) + */ + @discardableResult func onLink(_ closure: @escaping (String)->()) -> Self { + self.isUserInteractionEnabled = true + self.cpkLinkHandler = closure + return self + } +} + diff --git a/Cupcake/Stack.swift b/Cupcake/Stack.swift new file mode 100644 index 0000000..d2a5f22 --- /dev/null +++ b/Cupcake/Stack.swift @@ -0,0 +1,118 @@ +// +// Stack.swift +// Cupcake +// +// Created by nerdycat on 2017/3/29. +// Copyright © 2017 nerdycat. All rights reserved. +// + +import UIKit + +public enum CPKStackAlignment : Int { + case left //left alignment for VStack + case right //right alignment for VStack + + case center //center alignment for HStack and VStack + case fill //make subviews fill the the Stack + + case baseline //lastBaseline alignment for HStack + + //top alignment for HStack + public static var top: CPKStackAlignment { return CPKStackAlignment.left } + //bottom alignment for HStack + public static var bottom: CPKStackAlignment { return CPKStackAlignment.right } +} + + +/** + * Create horizontal/vertical StackView with items. + + * There are two special items: + 1. Number: represent individual gap between two items. + 2. String: represent a Spring that will take as much space as possible. + + * There are three way to specified spacing between items: + 1. Using Number as StackView item, which will add spacing between two items. + 2. Using .gap() method, which will add spacing to all items. + 3. Using .margin() method on UIView. A negative value of margin will add spacing around view. + + * If you use Number and .gap() at the same time, the individual spacing will take precedence. + + * By default, all items are enclosured inside StackView. + You can make a view exceeding the bounds of StackView by using a positive value of margin. + + * Usages: + HStack( + View.bg("random").pin(30, 30), + 20, + View.bg("random").pin(60, 60), + 20, + View.bg("random").pin(90, 90) + ).pin(.xy(20, 20)).addTo(self.view).border(1) + + VStack( + View.bg("random").pin(30, 30), + View.bg("random").pin(60, 60), + View.bg("random").pin(90, 90) + ).gap(20).pin(.xy(20, 140)).addTo(self.view).border(1) + + VStack( + HStack( + View.bg("random").pin(30, 30), + "<-->", + View.bg("random").pin(30, 30) + ), + View.bg("random").pin(30).margin(-10), + View.bg("random").pin(30).margin(0, 10) + ).embedIn(self.view, 390, 20, 20).border(1) + */ + +public func HStack(_ items: Any...) -> CPKStackView { + let stack = CPKStackView() + stack.axis = .horizontal + stack.alignment = .center + stack.addArrangedSubviews(items: items) + return stack +} + +public func VStack(_ items: Any...) -> CPKStackView { + let stack = CPKStackView() + stack.axis = .vertical + stack.alignment = .left + stack.addArrangedSubviews(items: items) + return stack +} + + +public extension CPKStackView { + + /** + * Universal gap between items. + * For individual gap between items, use Number as StackView item. + * Usages: + .gap(10) + */ + @discardableResult public func gap(_ spacing: CGFloat) -> Self { + self.spacing = spacing + return self + } + + /** + * StackView alignment + * For HStack, the default alignment is .center. + * For VStack, the default alignment is .left. + * Usages: + .align(.top) + .align(.fill) + ... + */ + @discardableResult public func align(_ alignment: CPKStackAlignment) -> Self { + self.alignment = alignment + return self + } +} + + + + + diff --git a/Cupcake/StaticTable.swift b/Cupcake/StaticTable.swift new file mode 100644 index 0000000..069bac3 --- /dev/null +++ b/Cupcake/StaticTable.swift @@ -0,0 +1,447 @@ +// +// StaticTable.swift +// Cupcake +// +// Created by nerdycat on 2017/4/21. +// Copyright © 2017 nerdycat. All rights reserved. +// + +import UIKit + +/** + * PlainTable and GroupTable allow you to create static tableView with ease. + * Usages: + + PlainTable("Option1", "Option2", "Option3").singleCheck().embedIn(self.view) + PlainTable(["Option1", "Option2", "Option3"]).multiCheck().embedIn(self.view) + + GroupTable( + Section( + Row.str("Row1").detail("detail1"), + Row.str("Row2").arrow().onClick({_ in print("row2") }) + ), + + Section( + Row.str("Row3").detail("detail3").style(.subtitle), + Row.str(AttStr("Row4").color("red")).switchOn().onChange({ row in + print(row.switchView.isOn) + }) + ).header(20), + + Row.custom({ row in + Label.str("Delete").color("red").align(.center).embedIn(row.cell.contentView) + }) + + ).embedIn(self.view) + */ + +public func PlainTable(_ sectionsOrRows: Any...) -> StaticTableView { + let tableView = StaticTableView(sectionsOrRows: sectionsOrRows, style: .plain) + tableView.tableFooterView = UIView() + return tableView +} + +public func GroupTable(_ sectionsOrRows: Any...) -> StaticTableView { + return StaticTableView(sectionsOrRows: sectionsOrRows, style: .grouped) +} + +public func Section(_ rowsOrStrings: Any...) -> StaticSection { + return StaticSection(rowsOrStrings: rowsOrStrings) +} + +public var Row: StaticRow { + return StaticRow() +} + + +public extension StaticTableView { + + /** + * Setting font for all titleLabels. + * If you only want to change one particular titleLabel's font, use AttStr instead. + * font use Font() internally, so it can take any kind of values that Font() supported. + * See Font.swift for more information. + * Usages: + .font(15) + .font("20") + .font("body") + .font(someLabel.font) + ... + */ + @discardableResult func font(_ any: Any) -> Self { + self.textFont = any + return self + } + + /** + * Setting textColor for all titleLabels. + * If you only want to change one particular titleLabel's textColor, use AttStr instead. + * color use Color() internally, so it can take any kind of values that Color() supported. + * See Color.swift for more information. + * Usages: + .color(@"red") + .color(@"#F00") + .color(@"255,0,0") + .color(someLabel.textColor) + ... + */ + @discardableResult func color(_ any: Any) -> Self { + self.textColor = any + return self + } + + /** + * Setting font for all detailTextLabels. + * If you only want to change one particular detailTextLabel's font, use AttStr instead. + * font use Font() internally, so it can take any kind of values that Font() supported. + * See Font.swift for more information. + * Usages: + .font(15) + .font("20") + .font("body") + .font(someLabel.font) + ... + */ + @discardableResult func detailFont(_ any: Any) -> Self { + self.detailFont = any + return self + } + + /** + * Setting textColor for all detailTextLabels. + * If you only want to change one particular detailTextLabel's textColor, use AttStr instead. + * color use Color() internally, so it can take any kind of values that Color() supported. + * See Color.swift for more information. + * Usages: + .color(@"red") + .color(@"#F00") + .color(@"255,0,0") + .color(someLabel.textColor) + ... + */ + @discardableResult func detailColor(_ any: Any) -> Self { + self.detailColor = any + return self + } + + /** + * Setting height for all cells. + * Usages: + .rowHeight(50) + .rowHeight(-1) //negative value means use UITableViewAutomaticDimension + */ + @discardableResult func rowHeight(_ height: CGFloat) -> Self { + self.cellHeight = height + return self + } + + /** + * Setting separator line indent for all cells. + * Usages: + .lineIndent(0) + .lineIndent(10) + */ + @discardableResult func lineIndent(_ indent: CGFloat) -> Self { + self.separatorIndent = indent + return self + } + + /** + * Shorthand for setting disclosureIndicator for all cells. + * Usages: + .arrow() + .arrow(false) + */ + @discardableResult func arrow(_ showArrow: Bool = true) -> Self { + self.accessoryType = (showArrow ? .disclosureIndicator : nil) + return self + } + + /** + * Call when cell is about to appear. + * You can use this method to further customize cell. + * Be aware of retain cycle when using this method. + * Usages: + .custom({ [weak self] row in + let cell = row.cell + let indexPath = row.indexPath + ... + }) + */ + @discardableResult func custom(_ handler: @escaping (StaticRow)->()) -> Self { + self.customHandler = handler + return self + } + + /** + * Call when cell is being selected. + * By passing a callback handler, all the cells will become selectable. + * Be aware of retain cycle when using this method. + * Usages: + .onClick({ [weak self] row in + let cell = row.cell + let indexPath = row.indexPath + ... + }) + */ + @discardableResult override func onClick(_ callback: @escaping (StaticRow)->()) -> Self { + self.onClickHandler = callback + return self + } +} + + +public extension StaticSection { + + /** + * Setting section header. + * By default, grouped style tableView' sections have default header and footer height. + * Header can be Number, String, View. + * Usages: + .header(10) //10 point header height, useful for changing GroupTableView's section gap. + .header("Header1") //header with string + .header(headerView) //header with view + */ + @discardableResult func header(_ any: Any) -> Self { + self.headerValue = any + return self + } + + /** + * Setting section footer. + * By default, grouped style tableView' sections have default header and footer height. + * Footer can be Number, String, View. + * Usages: + .footer(10) //10 point footer height, useful for changing GroupTableView's section gap. + .footer("Footer1") //footer with string + .footer(footerView) //footer with view + */ + @discardableResult func footer(_ any: Any) -> Self { + self.footerValue = any + return self + } + + /** + * Turning on single-check behavior for section. + * To find out which cells are being checked, use checkedIndexPaths property on StaticTableView. + * Usages: + .singleCheck() //use system checkmark + .singleCheck("checked") //use custom image + .singleCheck("checked", "unchecked") //use custom images + */ + @discardableResult func singleCheck(_ checkedImage: Any? = nil, _ uncheckedImage: Any? = nil) -> Self { + self.enableSingleCheck = true + self.checkedImage = checkedImage != nil ? Img(checkedImage!) : nil + self.uncheckedImage = uncheckedImage != nil ? Img(uncheckedImage!) : nil + return self + } + + /** + * Turning on multi-mheck behavior for section. + * To find out which cells are being checked, use checkedIndexPaths property on StaticTableView. + * Usages: + .multiCheck() //use system checkmark + .multiCheck("checked") //use custom image + .multiCheck("checked", "unchecked") //use custom images + */ + @discardableResult func multiCheck(_ checkedImage: Any? = nil, _ uncheckedImage: Any? = nil) -> Self { + self.enableMultiCheck = true + self.checkedImage = checkedImage != nil ? Img(checkedImage!) : nil + self.uncheckedImage = uncheckedImage != nil ? Img(uncheckedImage!) : nil + return self + } +} + + +public extension StaticRow { + + + /** + * Setting image for cell. + * img use Img() internally, so it can take any kind of values that Img() supported. + * See Img.swift for more information. + * Usages: + .img("cat") + .img("#button-background") + .img("$home-icon") + .img(someImage) + ... + */ + @discardableResult func img(_ any: Any) -> Self { + self.image = Img(any) + return self + } + + /** + * Setting text or attributedText for cell's textLabel. + * str can take any kind of value, even primitive type like Int. + * Usages: + .str(1024) + .str("hello world") + .str( AttStr("hello world").strikethrough() ) + ... + */ + @discardableResult func str(_ any: Any) -> Self { + self.text = any + return self + } + + /** + * Setting text or attributedText for cell's detailTextLabel. + * detail can take any kind of value, even primitive type like Int. + * Usages: + .detail(1024) + .detail("hello world") + .detail( AttStr("hello world").strikethrough() ) + ... + */ + @discardableResult func detail(_ any: Any) -> Self { + self.detailText = any + return self + } + + /** + * Setting cell style. + * Usages: + .style(.subtitle) + .style(.value2) + ... + */ + @discardableResult func style(_ style: UITableViewCellStyle) -> Self { + self.cellStyle = style + return self + } + + /** + * Setting accessoryType or accessoryView. + * Usages: + .accessory(.disclosureIndicator) + .accessory(.detailButton) + .accessory(.view(someView)) //setting accessoryView + ... + */ + @discardableResult func accessory(_ type: CPKTableViewCellAccessoryType) -> Self { + self.accessoryType = type + return self + } + + /** + * Shorthand for setting accessoryType with .disclosureIndicator. + * Usages: + .arrow() + .arrow(false) + */ + @discardableResult func arrow(_ showArrow: Bool = true) -> Self { + self.accessoryType = showArrow ? .disclosureIndicator : CPKTableViewCellAccessoryType.none + return self + } + + /** + * Shorthand for setting accessoryType with .checkmark. + * Usages: + .check() + .check(false) + */ + @discardableResult func check(_ checked: Bool = true) -> Self { + self.accessoryType = checked ? .checkmark : CPKTableViewCellAccessoryType.none + return self + } + + /** + * Shorthand for setting accessoryView with UISwitch. + * Usages: + .switchOn() + .switchOn(false) + */ + @discardableResult func switchOn(_ isOn: Bool = true) -> Self { + let sw: UISwitch! = self.switchView ?? {self.switchView = UISwitch(); return self.switchView}() + sw.isOn = isOn + self.accessory(.view(sw)) + return self + } + + /** + * Settting cell height. + * Usages: + .height(50) + .height(-1) //negative value means use UITableViewAutomaticDimension + */ + @discardableResult func height(_ height: CGFloat) -> Self { + self.cellHeight = height + return self + } + + /** + * Setting separator line indent. + * Usages: + .lineIndent(10) + */ + @discardableResult func lineIndent(_ indent: CGFloat) -> Self { + self.separatorIndent = indent + return self + } + + /** + * Call when cell is being selected. + * By passing a callback handler, the cell become selectable. + * Be aware of retain cycle when using this method. + * Usages: + .onClick({ [weak self] row in + let cell = row.cell + let indexPath = row.indexPath + ... + }) + */ + @discardableResult override func onClick(_ callback: @escaping (StaticRow)->()) -> Self { + self.onClickHandler = callback + return self + } + + /** + * Call when cell's detailButton is being clicked. + * Be aware of retain cycle when using this method. + * Usages: + .onButton({ [weak self] row in + let cell = row.cell + let indexPath = row.indexPath + ... + }) + */ + @discardableResult func onButton(_ callback: @escaping (StaticRow)->()) -> Self { + self.onButtonHandler = callback + return self + } + + /** + * Call when switch's value is changed. + * Be aware of retain cycle when using this method. + * Usages: + .onChange({ [weak self] row in + let sw = row.switchView + let indexPath = row.indexPath + ... + }) + */ + @discardableResult func onChange(_ callback: @escaping (StaticRow)->()) -> Self { + self.onChangeHandler = callback + return self + } + + /** + * Call when cell is about to appear. + * You can use this method to further customize cell. + * Usages: + .custom({ row in + let cell = row.cell + let indexPath = row.indexPath + ... + }) + */ + @discardableResult func custom(_ handler: @escaping (StaticRow)->()) -> Self { + self.customHandler = handler + return self + } +} + + + diff --git a/Cupcake/Str.swift b/Cupcake/Str.swift new file mode 100644 index 0000000..faffe13 --- /dev/null +++ b/Cupcake/Str.swift @@ -0,0 +1,160 @@ +// +// Str.swift +// Cupcake +// +// Created by nerdycat on 2017/3/17. +// Copyright © 2017 nerdycat. All rights reserved. +// + +import UIKit + +/** + * Create String from formatted string. + * Also can be use to convert any type to String. + * Usages: + Str("1+1=%d", 2) //"1+1=2" + Str(1024) //"1024" + Str(3.14) //"3.14" + ... + */ +public func Str(_ any: Any, _ arguments: CVarArg...) -> String { + let str = String(format: String(describing: any), arguments: Array(arguments)) + return str +} + + +public extension String { + + /** + * Return substring from index or to some particular string. + * Usages: + "hello world".subFrom(6) //"world" + "hello world".subFrom(-5) //"world" + "hello world".subFrom("wo") //"world" + */ + @discardableResult public func subFrom(_ indexOrSubstring: Any) -> String { + if var index = indexOrSubstring as? Int { + if index < 0 { index += self.characters.count } + return self.substring(from: self.index(self.startIndex, offsetBy: index)) + + } else if let substr = indexOrSubstring as? String { + if let range = self.range(of: substr) { + return self.substring(from: range.lowerBound) + } + } + + return "" + } + + + /** + * Return substring to index or to some particular string. + * Usages: + "hello world".subTo(5) //"hello" + "hello world".subTo(-6) //"hello" + "hello world".subTo(" ") //"hello" + */ + @discardableResult public func subTo(_ indexOrSubstring: Any) -> String { + if var index = indexOrSubstring as? Int { + if index < 0 { index += self.characters.count } + return self.substring(to: self.index(self.startIndex, offsetBy: index)) + + } else if let substr = indexOrSubstring as? String { + if let range = self.range(of: substr) { + return self.substring(to: range.lowerBound) + } + } + + return "" + } + + + /** + * Return substring at index or in range. + * Usages: + "hello world".subAt(1) //"e" + "hello world".subAt(1..<4) //"ell" + */ + @discardableResult public func subAt(_ indexOrRange: Any) -> String { + if let index = indexOrRange as? Int { + return String(self[self.index(self.startIndex, offsetBy: index)]) + + } else if let range = indexOrRange as? Range { + return self.substring(with: range) + + } else if let range = indexOrRange as? Range { + let lower = self.index(self.startIndex, offsetBy: range.lowerBound) + let upper = self.index(self.startIndex, offsetBy: range.upperBound) + return self.substring(with: lower.. { + let lower = self.index(self.startIndex, offsetBy: range.lowerBound) + let upper = self.index(self.startIndex, offsetBy: range.upperBound) + return self.substring(with: lower.. { + let lower = self.index(self.startIndex, offsetBy: range.lowerBound) + let upper = self.index(self.startIndex, offsetBy: range.upperBound + 1) + return self.substring(with: lower.. { + let lower = self.index(self.startIndex, offsetBy: range.lowerBound) + let upper = self.index(self.startIndex, offsetBy: range.upperBound + 1) + return self.substring(with: lower.. String { + let options = NSRegularExpression.Options(rawValue: 0) + + if let exp = try? NSRegularExpression(pattern: pattern, options: options) { + let options = NSRegularExpression.MatchingOptions(rawValue: 0) + + let matchRange = exp.rangeOfFirstMatch(in: self, + options:options, + range: NSMakeRange(0, self.characters.count)) + + if matchRange.location != NSNotFound { + return self.subAt(matchRange) + } + } + + return "" + } + + + /** + * Replace substring with template. + * Usages: + "abc123".subReplace("abc", "ABC") //ABC123 + "abc123".subReplace("([a-z]+)(\\d+)", "$2$1") //"123abc" + */ + @discardableResult public func subReplace(_ pattern: String, _ template: String) -> String { + let options = NSRegularExpression.Options(rawValue: 0) + + if let exp = try? NSRegularExpression(pattern: pattern, options: options) { + let options = NSRegularExpression.MatchingOptions(rawValue: 0) + + return exp.stringByReplacingMatches(in: self, + options: options, + range: NSMakeRange(0, self.characters.count), + withTemplate: template) + } + + return "" + } +} + diff --git a/Cupcake/Styles.swift b/Cupcake/Styles.swift new file mode 100644 index 0000000..0697fb9 --- /dev/null +++ b/Cupcake/Styles.swift @@ -0,0 +1,160 @@ +// +// Styles.swift +// Cupcake +// +// Created by nerdycat on 2017/5/3. +// Copyright © 2017 nerdycat. All rights reserved. +// + +import UIKit + + +/** + * Some of the chainable property can be set as style. + + * There are two type of styles: + 1) Global style: + Styles("GlobalStyleName").color("red").font(15) + 2) Local style: + let localStyle = Styles.color("red").font(15) + + * After creation, you can apply styles to any UIView: + Label.styles("GlobalStyleName", localStyle) + Button.styles(localStyle).font(17) + */ + +public var Styles: StylesMaker { + return StylesMaker() +} + +public func Styles(_ name: String) -> StylesMaker { + let makers = StylesMaker() + StylesMaker.globalStyles[name] = makers + return makers +} + + +public extension StylesMaker { + + //View + @discardableResult public func bg(_ any: Any) -> Self { + return addStyle(key: #function, value: any) + } + + @discardableResult public func tint(_ any: Any) -> Self { + return addStyle(key: #function, value: any) + } + + @discardableResult public func radius(_ cornerRadius: CGFloat) -> Self { + return addStyle(key: #function, value: cornerRadius) + } + + @discardableResult public func border(_ borderWidth: CGFloat, _ borderColor: Any? = nil) -> Self { + var dict: Dictionary = ["borderWidth": borderWidth] + if let color = borderColor { + dict["borderColor"] = color + } + return addStyle(key: #function, value: dict) + } + + @discardableResult public func shadow(_ shadowOpacity: CGFloat, + _ shadowRadius: CGFloat = 3, + _ shadowOffsetX: CGFloat = 0, + _ shadowOffsetY: CGFloat = 3) -> Self { + + let dict: Dictionary = ["opacity": shadowOpacity, + "radius": shadowRadius, + "offsetX": shadowOffsetX, + "offsetY": shadowOffsetY] + + return addStyle(key: #function, value: dict) + } + + @discardableResult public func pin(_ options: CPKViewPinOptions...) -> Self { + return addStyle(key: #function, value: options) + } + + + //Label + @discardableResult public func str(_ any: Any) -> Self { + return addStyle(key: #function, value: any) + } + + @discardableResult public func font(_ any: Any) -> Self { + return addStyle(key: #function, value: any) + } + + @discardableResult public func color(_ any: Any) -> Self { + return addStyle(key: #function, value: any) + } + + @discardableResult public func lines(_ numberOfLines: CGFloat = 0) -> Self { + return addStyle(key: #function, value: numberOfLines) + } + + @discardableResult public func lineGap(_ lineSpacing: CGFloat) -> Self { + return addStyle(key: #function, value: lineSpacing) + } + + @discardableResult public func align(_ textAlignment: NSTextAlignment) -> Self { + return addStyle(key: #function, value: textAlignment) + } + + + //ImageView + @discardableResult public func img(_ any: Any) -> Self { + return addStyle(key: #function, value: any) + } + + @discardableResult public func mode(_ contentMode: UIViewContentMode) -> Self { + return addStyle(key: #function, value: contentMode) + } + + + //Button + @discardableResult public func highColor(_ any: Any) -> Self { + return addStyle(key: #function, value: any) + } + + @discardableResult public func highBg(_ any: Any) -> Self { + return addStyle(key: #function, value: any) + } + + @discardableResult public func padding(_ contentEdgeInsets: CGFloat...) -> Self { + return addStyle(key: #function, value: contentEdgeInsets) + } + + @discardableResult public func gap(_ spacing: CGFloat) -> Self { + return addStyle(key: #function, value: spacing) + } + + @discardableResult public func reversed(_ reversed: Bool = true) -> Self { + return addStyle(key: #function, value: reversed) + } + + + //TextField + @discardableResult public func maxLength(_ length: CGFloat) -> Self { + return addStyle(key: #function, value: length) + } + + @discardableResult public func secure(_ secureTextEntry: Bool = true) -> Self { + return addStyle(key: #function, value: secureTextEntry) + } + + @discardableResult public func keyboard(_ keyboardType: UIKeyboardType) -> Self { + return addStyle(key: #function, value: keyboardType) + } + + @discardableResult public func returnKey(_ returnKeyType: UIReturnKeyType) -> Self { + return addStyle(key: #function, value: returnKeyType) + } + + @discardableResult public func clearMode(_ clearButtonMode: UITextFieldViewMode) -> Self { + return addStyle(key: #function, value: clearButtonMode) + } +} + + + + diff --git a/Cupcake/TextField.swift b/Cupcake/TextField.swift new file mode 100644 index 0000000..a73e85d --- /dev/null +++ b/Cupcake/TextField.swift @@ -0,0 +1,198 @@ +// +// TextField.swift +// Cupcake +// +// Created by nerdycat on 2017/3/24. +// Copyright © 2017 nerdycat. All rights reserved. +// + +import UIKit + +var TextField: UITextField { + let textField = UITextField() + textField.enablesReturnKeyAutomatically = true + textField.returnKeyType = .done + return textField +} + +extension UITextField { + + /** + * Setting text or attributedText + * str can take any kind of value, even primitive type like Int. + * Usages: + .str(1024) + .str("hello world") + .str( AttStr("hello world").strikethrough() ) + ... + */ + @discardableResult public func str(_ any: Any) -> Self { + if let attStr = any as? NSAttributedString { + self.attributedText = attStr + } else { + self.text = String(describing: any) + } + return self + } + + /** + * Setting placeholder or attributedPlaceholder + * hint can take any kind of value, even primitive type like Int. + * Usages: + .hint(1024) + .hint("Enter your name") + .hint( AttStr("Enter your name").font(13) ) + */ + @discardableResult public func hint(_ any: Any) -> Self { + if let attStr = any as? NSAttributedString { + self.attributedPlaceholder = attStr + } else { + self.placeholder = String(describing: any) + } + return self + } + + /** + * Setting font + * font use Font() internally, so it can take any kind of values that Font() supported. + * See Font.siwft for more information. + * Usages: + .font(15) + .font("20") + .font("body") + .font(someLabel.font) + ... + **/ + @discardableResult public func font(_ any: Any) -> Self { + self.font = Font(any) + return self + } + + /** + * Setting textColor + * color use Color() internally, so it can take any kind of values that Color() supported. + * See Color.swift for more information. + * Usages: + .color(@"red") + .color(@"#F00") + .color(@"255,0,0") + .color(someLabel.textColor) + ... + */ + @discardableResult public func color(_ any: Any) -> Self { + self.textColor = Color(any) + return self + } + + /** + * Max input length + * Usages: + .maxLength(10) + */ + @discardableResult public func maxLength(_ length: CGFloat) -> Self { + self.cpkMaxLength = Int(length) + return self + } + + /** + * contentEdgeInsets for UITextField + * Usages: + .padding(10) //top: 10, left: 10, bottom: 10, right: 10 + .padding(10, 20) //top: 10, left: 20, bottom: 10, right: 20 + .padding(10, 20, 30) //top: 10, left: 20, bottom: 0 , right: 30 + .padding(10, 20, 30, 40) //top: 10, left: 20, bottom: 30, right: 40 + */ + @discardableResult public func padding(_ contentEdgeInsets: CGFloat...) -> Self { + cpk_updatePadding(contentEdgeInsets, forView: self) + return self + } + + /** + * secureTextEntry + * Usages: + .secure() //secureTextEntry = true + .secure(false) //secureTextEntry = false + */ + @discardableResult public func secure(_ secureTextEntry: Bool = true) -> Self { + self.isSecureTextEntry = secureTextEntry + return self + } + + /** + * Setting textAlignment + * Usages: + .align(.center) + .align(.justified) + ... + */ + @discardableResult public func align(_ textAlignment: NSTextAlignment) -> Self { + self.textAlignment = textAlignment + return self + } + + /** + * Setting keyboardType + * Usages: + .keyboard(.numberPad) + .keyboard(.emailAddress) + ... + */ + @discardableResult public func keyboard(_ keyboardType: UIKeyboardType) -> Self { + self.keyboardType = keyboardType + return self + } + + /** + * Setting returnKeyType + * Usages: + .returnKey(.send) + .returnKey(.google) + ... + */ + @discardableResult public func returnKey(_ returnKeyType: UIReturnKeyType) -> Self { + self.returnKeyType = returnKeyType + return self + } + + /** + * Setting clearButtonMode + * Usages: + .clearMode(.whileEditing) + .clearMode(.always) + ... + */ + @discardableResult public func clearMode(_ clearButtonMode: UITextFieldViewMode) -> Self { + self.clearButtonMode = clearButtonMode + return self + } + + /** + * Setup text changed handler. + * Be aware of retain cycle when using this method. + * Usages: + .onChange({ textField in /* ... */ }) + .onChange({ _ in /* ... */ }) + .onChange({ [weak self] textField in /* ... */ }) //capture self as weak reference when needed + */ + @discardableResult func onChange(_ closure: @escaping (UITextField)->()) -> Self { + self.cpkTextChangedClosure = cpk_generateCallbackClosure(closure, nil) + return self + } + + /** + * Setup enter finished handler. + * The handler will be called when user click the return button. + * Be aware of retain cycle when using this method. + * Usages: + .onFinish({ textField in /* ... */ }) + .onFinish({ _ in /* ... */ }) + .onFinish({ [weak self] textField in /* ... */ }) //capture self as weak reference when needed + */ + @discardableResult func onFinish(_ closure: @escaping (UITextField)->()) -> Self { + self.cpkDidEndOnExistClosure = cpk_generateCallbackClosure(closure, nil) + return self + } +} + + + diff --git a/Cupcake/TextView.swift b/Cupcake/TextView.swift new file mode 100644 index 0000000..a9075ae --- /dev/null +++ b/Cupcake/TextView.swift @@ -0,0 +1,130 @@ +// +// TextView.swift +// Cupcake +// +// Created by nerdycat on 2017/3/25. +// Copyright © 2017 nerdycat. All rights reserved. +// + +import UIKit + +var TextView: UITextView { + return UITextView().font(17) +} + +extension UITextView { + + /** + * Setting text or attributedText + * str can take any kind of value, even primitive type like Int. + * Usages: + .str(1024) + .str("hello world") + .str( AttStr("hello world").strikethrough() ) + ... + */ + @discardableResult public func str(_ any: Any) -> Self { + if let attStr = any as? NSAttributedString { + self.attributedText = attStr + } else { + self.text = String(describing: any) + } + return self + } + + /** + * Setting placeholder or attributedPlaceholder + * hint can take any kind of value, even primitive type like Int. + * Usages: + .hint(1024) + .hint("Enter here") + .hint( AttStr("Enter here").font(13) ) + */ + @discardableResult public func hint(_ any: Any) -> Self { + cpk_setPlaceholder(any) + return self + } + + /** + * Setting font + * font use Font() internally, so it can take any kind of values that Font() supported. + * See Font.siwft for more information. + * Usages: + .font(15) + .font("20") + .font("body") + .font(someLabel.font) + ... + **/ + @discardableResult public func font(_ any: Any) -> Self { + self.font = Font(any) + return self + } + + /** + * Setting textColor + * color use Color() internally, so it can take any kind of values that Color() supported. + * See Color.swift for more information. + * Usages: + .color(@"red") + .color(@"#F00") + .color(@"255,0,0") + .color(someLabel.textColor) + ... + */ + @discardableResult public func color(_ any: Any) -> Self { + self.textColor = Color(any) + return self + } + + /** + * Max input length + * Usages: + .maxLength(10) + */ + @discardableResult public func maxLength(_ length: CGFloat) -> Self { + self.cpkMaxLength = Int(length) + return self + } + + /** + * contentEdgeInsets for UITextView + * Usages: + .padding(10) //top: 10, left: 10, bottom: 10, right: 10 + .padding(10, 20) //top: 10, left: 20, bottom: 10, right: 20 + .padding(10, 20, 30) //top: 10, left: 20, bottom: 0 , right: 30 + .padding(10, 20, 30, 40) //top: 10, left: 20, bottom: 30, right: 40 + */ + @discardableResult public func padding(_ contentEdgeInsets: CGFloat...) -> Self { + cpk_updatePadding(contentEdgeInsets, forView: self) + return self + } + + /** + * Setting textAlignment + * Usages: + .align(.center) + .align(.justified) + ... + */ + @discardableResult public func align(_ textAlignment: NSTextAlignment) -> Self { + self.textAlignment = textAlignment + return self + } + + /** + * Setup text changed handler. + * Be aware of retain cycle when using this method. + * Usages: + .onChange({ textView in /* ... */ }) + .onChange({ _ in /* ... */ }) + .onChange({ [weak self] textView in /* ... */ }) //capture self as weak reference when needed + */ + @discardableResult func onChange(_ closure: @escaping (UITextView)->()) -> Self { + self.cpkTextChangedClosure = cpk_generateCallbackClosure(closure, nil) + return self + } +} + + + diff --git a/Cupcake/View.swift b/Cupcake/View.swift new file mode 100644 index 0000000..f3260f0 --- /dev/null +++ b/Cupcake/View.swift @@ -0,0 +1,362 @@ +// +// View.swift +// Cupcake +// +// Created by nerdycat on 2017/3/22. +// Copyright © 2017 nerdycat. All rights reserved. +// + +import UIKit + +public var View: UIView { + let view = UIView() + return view +} + +extension UIView { + + /** + * Setting background with Color or Image. + * bg use Color() internally, so it can take any kind of values that Color() supported. + * See Color.swift for more information. + * Usages: + .bg(@"red") + .bg(@"#F00") + .bg(@"255,0,0") + .bg(someView.backgroundColor) + .bg("cat") //using image + ... + */ + @discardableResult public func bg(_ any: Any) -> Self { + self.backgroundColor = Color(any) ?? Color(Img(any)) + return self + } + + /** + * Setting tintColor + * tint use Color() internally, so it can take any kind of values that Color() supported. + * See Color.swift for more information. + * Usages: + .tint("red") + .tint("#00F") + */ + @discardableResult public func tint(_ any: Any) -> Self { + self.tintColor = Color(any) + return self + } + + /** + * Setting cornerRadius + * radius support auto rounding, which is a very useful trick when working with AutoLayout. + * Auto rounding will alway set cornerRadius to half of the height not matter what size it is. + * Usages: + .radius(10) + .radius(-1) //passing negative number means using auto rounding + */ + @discardableResult public func radius(_ cornerRadius: CGFloat) -> Self { + if cornerRadius >= 0 { + self.layer.cornerRadius = cornerRadius + self.cpkAutoRoundingRadius = false + } else { + self.layer.cornerRadius = self.bounds.height / 2 + self.cpkAutoRoundingRadius = true + } + + cpk_masksToBoundsIfNeed() + return self + } + + /** + * Setting border with borderWidth and borderColor (optional). + * border's second argument use Color() internally, so it can take any kind of values that Color() supported. + * See Color.swift for more information. + * Usages: + .border(1) + .border(1, "red") + */ + @discardableResult public func border(_ borderWidth: CGFloat, _ borderColor: Any? = nil) -> Self { + self.layer.borderWidth = borderWidth + self.layer.borderColor = Color(borderColor)?.cgColor + return self + } + + /** + * Drop shadow with opacity, radius (optional) and offset (optional). + * Usages: + .shadow(1) + .shadow(0.7, 2) + .shadow(0.7, 3, 0, 0) + */ + @discardableResult public func shadow(_ shadowOpacity: CGFloat, + _ shadowRadius: CGFloat = 3, + _ shadowOffsetX: CGFloat = 0, + _ shadowOffsetY: CGFloat = 3) -> Self { + + self.layer.shadowOpacity = Float(shadowOpacity) + self.layer.shadowRadius = shadowRadius + self.layer.shadowOffset = CGSize(width: shadowOffsetX, height: shadowOffsetY) + return self + } + + + /** + * Apply at most 4 styles to view. + * See Styles.swift for more information. + Usages: + .styles(myStyle) + .styles(myStyle1, myStyle2, "globalStyle1") + */ + @discardableResult public func styles(_ s1: Any, _ s2: Any? = nil, _ s3: Any? = nil, _ s4: Any? = nil) -> Self { + var array = Array() + array.append(s1) + + if s2 != nil { array.append(s2!) } + if s3 != nil { array.append(s3!) } + if s4 != nil { array.append(s4!) } + + for styles in array { + if let style = styles as? StylesMaker { + style.applyTo(view: self) + + } else if let name = styles as? String { + if let style = StylesMaker.globalStyles[name] { + style.applyTo(view: self) + } + } + } + return self + } + + /** + * Setting touch insets. + * Very useful for extending touch area. + * It can take variety of forms. + * Usages: + .touchInsets(10) //top: 10, left: 10, bottom: 10, right: 10 + .touchInsets(10, 20) //top: 10, left: 20, bottom: 10, right: 20 + .touchInsets(10, 20, 30) //top: 10, left: 20, bottom: 0 , right: 30 + .touchInsets(10, 20, 30, 40) //top: 10, left: 20, bottom: 30, right: 40 + */ + @discardableResult public func touchInsets(_ p1: Any, _ p2: Any? = nil, _ p3: Any? = nil, _ p4: Any? = nil) -> Self { + self.cpkTouchInsets = cpk_edgeInsetsFromParameters(p1, p2, p3, p4) + return self + } + + /** + * Setup click handler. + * This will automatically set isUserInteractionEnabled to true as well. + * Be aware of retain cycle when using this method. + * Usages: + .onClick({ view in /* ... */ }) //the view being clicked is pass as parameter + .onClick({ _ in /* ... */ }) //if you don't care at all + .onClick({ [weak self] _ in /* ... */ }) //capture self as weak reference when needed + */ + @discardableResult func onClick(_ closure: @escaping (UIView)->()) -> Self { + cpk_onClick(closure, nil) + return self + } + + /** + * Add current view to superview. + * Usages: + .addTo(superView) + */ + @discardableResult public func addTo(_ superview: UIView) -> Self { + superview.addSubview(self) + return self + } + + /** + * Add multiply subviews at the same time. + * Usages: + .addSubview(view1, view2, view3) + */ + @discardableResult public func addSubviews(_ children: Any...) -> Self { + func addChildren(_ children: Array) { + for child in children { + if let view = child as? UIView { + self.addSubview(view) + } else if let array = child as? Array { + addChildren(array) + } + } + } + + addChildren(children) + return self + } +} + + + +public extension UIView { + + /** + * Setting layout margins. + * Very useful when embed in StackView. + * It can take variety of forms. + * Usages: + .margin(10) //top: 10, left: 10, bottom: 10, right: 10 + .margin(10, 20) //top: 10, left: 20, bottom: 10, right: 20 + .margin(10, 20, 30) //top: 10, left: 20, bottom: 0 , right: 30 + .margin(10, 20, 30, 40) //top: 10, left: 20, bottom: 30, right: 40 + */ + @discardableResult public func margin(_ p1: Any, _ p2: Any? = nil, _ p3: Any? = nil, _ p4: Any? = nil) -> Self { + self.layoutMargins = cpk_edgeInsetsFromParameters(p1, p2, p3, p4) + return self + } + + /** + * An easy way to setup constraints relative to superview. + * You can pass multiply options at the same time. + + * Usages: + .pin(.x(20)) //add left constraint + .pin(.y(20)) //add top constraint + .pin(.xy(20, 20)) //and left and top constraints + + .pin(.w(100)) //add width constraint + .pin(.h(100)) //add height constraint + .pin(.wh(100, 100)) //add width and height constraints + + .pin(.xywh(20, 20, 100, 100)) //add left, top, width and height constraints + + .pin(.maxX(-20)) //add right constraint + .pin(.maxY(-20)) //add bottom constraint + .pin(.maxXY(-20, -20)) //add right and bottom constraints + + .pin(.centerX(0)) //add centerX constriant + .pin(.centerY(0)) //add centerY constraint + .pin(.centerXY(0, 0)) //add centerX and centerY constraints + .pin(.center) //add centerX and centerY constraints with offset of 0 + + .pin(.whRatio(2)) //width = height * 2 constraint + .pin(.ratio) //width = height * currentSizeRatio constraint, very useful for ImageView + + .pin(.lowHugging) //Low content hugging priority, very useful when embed in StackView. + //The lowest hugging view will take as much space as possible. + + .pin(.lowRegistance) //Low content compression resistance priority, very useful when embed in StackView. + //The lowest resistance view will be compressed when there are no more space available. + + .pin(100) //shorthand for .pin(.h(100)) + .pin(100, 100) //shorthand for .pin(.wh(100, 100)) + .pin(20, 20, 100, 100) //shorthand for .pin(.xywh(20, 20, 100, 100)) + + //pass multiply options at the same time + .pin(.xy(20, 20), .maxX(-20), 100) + .pin(100, 100, .center) + ... + */ + @discardableResult public func pin(_ options: CPKViewPinOptions...) -> Self { + cpk_pinOptions(options, forView: self) + return self + } + + /** + * Making constraints just like SnapKit. + * makeCons will only create new constraints when needed. + * Usages: + .makeCons({ + _.left.top.equal(someView).offset(20, 20) + _.size.equal(100, 100) + }) + */ + @discardableResult public func makeCons(_ closure: (ConsMaker)->()) -> Self { + let maker = ConsMaker(firstItem: self) + closure(maker) + maker.updateConstraints() + return self + } + + /** + * Remake constarints just like SnapKit. + * remakeCons will remove all previous installed constarints first. + * Usages: + .makeCons({ + _.center.equal(someView) + _.size.equal(anotherView) + }) + */ + @discardableResult public func remakeCons(_ closure: (ConsMaker)->()) -> Self { + let maker = ConsMaker(firstItem: self) + closure(maker) + maker.remakeConstraints() + return self + } + + /** + * Embed self into superview with optional edge constraints. + * Passing nil means no constraint. + * Usages: + .embedIn(superview) //top: 0, left: 0, bottom: 0, right: 0 + .embedIn(superview, 10) //top: 10, left: 10, bottom: 10, right: 10 + .embedIn(superview, 10, 20) //top: 10, left: 20, bottom: 10, right: 20 + .embedIn(superview, 10, 20, 30) //top: 10, left: 20, right: 30 + .embedIn(superview, 10, 20, 30, 40) //top: 10, left: 20, bottom: 30, right: 40 + .embedIn(superview, 10, 20, nil) //top: 10, left: 20 + */ + @discardableResult + public func embedIn(_ superview: UIView, + _ p1: Any? = "", _ p2: Any? = "", + _ p3: Any? = "", _ p4: Any? = "") -> Self { + + superview.addSubview(self) + let edge = cpk_edgeInsetsTupleFromParameters(p1, p2, p3, p4) + + makeCons({ + if let top = edge.0 { + $0.top.offset(top) + } + if let left = edge.1 { + $0.left.offset(left) + } + if let bottom = edge.2 { + $0.bottom.offset(-bottom) + } + if let right = edge.3 { + $0.right.offset(-right) + } + }) + + return self + } +} + + +public enum CPKViewPinOptions { + case x(CGFloat) //left constraint + case y(CGFloat) //top constraint + case xy(CGFloat, CGFloat) //left and top constraints + + case w(CGFloat) //width constraint + case h(CGFloat) //height constraint + case wh(CGFloat, CGFloat) //width and height constraints + + case xywh(CGFloat, CGFloat, //left, top, width and height constraints + CGFloat, CGFloat) + + case maxX(CGFloat) //right constraint + case maxY(CGFloat) //bottom constraint + case maxXY(CGFloat, CGFloat) //right and bottom constraint + + case centerX(CGFloat) //centerX constraint + case centerY(CGFloat) //centerY constraint + case centerXY(CGFloat, CGFloat) //centerX and centerY constraints + case center //centerX and centerY constraints with offset of 0 + + case whRatio(CGFloat) //width = height * ratio constraint + case ratio //width = height * currentSizeRatio constraint + + case hhp(UILayoutPriority) //horizontal content hugging priority + case vhp(UILayoutPriority) //vertical content hugging priority + case hrp(UILayoutPriority) //horizontal content compression resistance priority + case vrp(UILayoutPriority) //vertical content compression resistance priority + + case lowHugging //low content hugging priority + case lowResistance //low content compression resistance priority + + case ___value(CGFloat) //private usage +} + + diff --git a/Cupcake/__Privates__Implementation__.swift b/Cupcake/__Privates__Implementation__.swift new file mode 100644 index 0000000..4feaad3 --- /dev/null +++ b/Cupcake/__Privates__Implementation__.swift @@ -0,0 +1,3312 @@ +// +// __Privates__.swift +// Cupcake +// +// Created by nerdycat on 2017/6/8. +// Copyright © 2017 nerdycat. All rights reserved. +// + + +import UIKit +import UIKit.UIGestureRecognizerSubclass + + + +/** + * Note: + * This file contains most of the implementation codes. + * You don't have to know the details while using Cupcake. + */ + + + + + + +//MARK: Utils + +func Sel(_ any: Any) -> Selector? { + if let sel = any as? Selector { + return sel + + } else if let selString = any as? String { + return NSSelectorFromString(selString) + + } else { + return nil + } +} + +func CPKFloat(_ any: Any?) -> CGFloat { + if any == nil { return 0 } + + if let result = CPKFloatOptional(any) { + return result + } else { + assert(false, "invalid float") + } +} + +func CPKFloatOptional(_ any: Any?) -> CGFloat? { + + if any == nil { + return nil + + } else { + if let value = any as? CGFloat { return value } + if let value = any as? String { let f = Float(value); return f != nil ? CGFloat(f!) : nil } + + if let value = any as? Int { return CGFloat(value) } + if let value = any as? UInt { return CGFloat(value) } + + if let value = any as? Double { return CGFloat(value) } + if let value = any as? Float { return CGFloat(value) } + + if let value = any as? Int8 { return CGFloat(value) } + if let value = any as? UInt8 { return CGFloat(value) } + if let value = any as? Int16 { return CGFloat(value) } + if let value = any as? UInt16 { return CGFloat(value) } + if let value = any as? Int32 { return CGFloat(value) } + if let value = any as? UInt32 { return CGFloat(value) } + if let value = any as? Int64 { return CGFloat(value) } + if let value = any as? UInt64 { return CGFloat(value) } + + return nil + } +} + +func cpk_onePointImageWithColor(_ color: UIColor) -> UIImage { + let rect = CGRect(x: 0, y: 0, width: 1, height: 1) + let hasAlpha = cpk_colorHasAlphaChannel(color) + UIGraphicsBeginImageContextWithOptions(rect.size, !hasAlpha, UIScreen.main.scale) + + let context = UIGraphicsGetCurrentContext()! + context.setFillColor(color.cgColor) + context.fill(rect) + + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + return image! +} + +fileprivate func cpk_colorHasAlphaChannel(_ color: UIColor) -> Bool { + return color.cgColor.alpha < 1; +} + +func cpk_imageHasAlphaChannel(_ image: UIImage) -> Bool { + if let alphaInfo = image.cgImage?.alphaInfo { + return alphaInfo == .first || + alphaInfo == .last || + alphaInfo == .premultipliedFirst || + alphaInfo == .premultipliedLast + } else { + return false + } +} + +func cpk_edgeInsetsFromArray(_ insetArray: [CGFloat]) -> UIEdgeInsets { + if insetArray.count == 0 { + return UIEdgeInsetsMake(0, 0, 0, 0) + + } else if insetArray.count == 1 { + let m1 = insetArray[0] + return UIEdgeInsetsMake(m1, m1, m1, m1) + + } else if insetArray.count == 2 { + let m1 = insetArray[0] + let m2 = insetArray[1] + return UIEdgeInsetsMake(m1, m2, m1, m2) + + } else if insetArray.count == 3 { + let m1 = insetArray[0] + let m2 = insetArray[1] + let m3 = insetArray[2] + return UIEdgeInsetsMake(m1, m2, 0, m3) + + } else { + let m1 = insetArray[0] + let m2 = insetArray[1] + let m3 = insetArray[2] + let m4 = insetArray[3] + return UIEdgeInsetsMake(m1, m2, m3, m4) + } +} + +func cpk_edgeInsetsFromParameters(_ p1: Any, _ p2: Any? = nil, _ p3: Any? = nil, _ p4: Any? = nil) -> UIEdgeInsets { + var array = [CGFloat]() + array.append(CPKFloat(p1)) + + if p2 != nil { array.append(CPKFloat(p2!)) } + if p3 != nil { array.append(CPKFloat(p3!)) } + if p4 != nil { array.append(CPKFloat(p4!)) } + + return cpk_edgeInsetsFromArray(array) +} + +func cpk_edgeInsetsTupleFromParameters(_ p1: Any? = "", + _ p2: Any? = "", + _ p3: Any? = "", + _ p4: Any? = "") + + -> (CGFloat?, CGFloat?, CGFloat?, CGFloat?) { + + var leftOffset: CGFloat? + var rightOffset: CGFloat? + var topOffset: CGFloat? + var bottomOffset: CGFloat? + + let isValidP1 = !(p1 is String) + let isValidP2 = !(p2 is String) + let isValidP3 = !(p3 is String) + let isValidP4 = !(p4 is String) + + if isValidP1 && isValidP2 && isValidP3 && isValidP4 { + topOffset = CPKFloatOptional(p1) + leftOffset = CPKFloatOptional(p2) + bottomOffset = CPKFloatOptional(p3) + rightOffset = CPKFloatOptional(p4) + + } else if isValidP1 && isValidP2 && isValidP3 { + topOffset = CPKFloatOptional(p1) + leftOffset = CPKFloatOptional(p2) + rightOffset = CPKFloatOptional(p3) + + } else if isValidP1 && isValidP2 { + topOffset = CPKFloatOptional(p1) + leftOffset = CPKFloatOptional(p2) + bottomOffset = CPKFloatOptional(p1) + rightOffset = CPKFloatOptional(p2) + + } else if isValidP1 { + topOffset = CPKFloatOptional(p1) + leftOffset = CPKFloatOptional(p1) + bottomOffset = CPKFloatOptional(p1) + rightOffset = CPKFloatOptional(p1) + + } else { + topOffset = 0 + leftOffset = 0 + bottomOffset = 0 + rightOffset = 0 + } + + return (topOffset, leftOffset, bottomOffset, rightOffset) +} + +func cpk_reversedEdgeInsetsFromArray(_ insetArray: [CGFloat]) -> UIEdgeInsets { + var insets = cpk_edgeInsetsFromArray(insetArray) + insets.top = -insets.top + insets.left = -insets.left + insets.bottom = -insets.bottom + insets.right = -insets.right + return insets +} + +func cpk_higherHuggingAndResistance(forView view: UIView) { + view.setContentHuggingPriority(251, for: .horizontal) + view.setContentHuggingPriority(251, for: .vertical) + view.setContentCompressionResistancePriority(751, for: .horizontal) + view.setContentCompressionResistancePriority(751, for: .vertical) +} + +func cpk_limitTextInput(_ textInput: UITextInput, maxLength: Int) -> Bool { + + if textInput.markedTextRange?.start != nil { + return true + + } else { + if maxLength > 0, let view = textInput as? UIView { + if let text = view.value(forKey: "text") as? String { + + if text.characters.count > maxLength { + let newText = text.subTo(maxLength) + var needResetCursorPosition = false + + let selectedRange = textInput.selectedTextRange + + if selectedRange != nil && selectedRange!.isEmpty { + let cursorPosition = selectedRange!.start + + if let maxPosition = textInput.position(from: textInput.beginningOfDocument, + offset: maxLength) { + if textInput.compare(cursorPosition, to: maxPosition) == .orderedAscending { + needResetCursorPosition = true + } + } + } + + view.setValue(newText, forKey: "text") + + if needResetCursorPosition { + textInput.selectedTextRange = selectedRange + } + } + } + } + + return false + } +} + +func cpk_getTopViewController() -> UIViewController? { + func getTopViewController(root: UIViewController?) -> UIViewController? { + if let navVC = root as? UINavigationController { + return getTopViewController(root: navVC.viewControllers.last) + + } else if let tabVC = root as? UITabBarController { + return getTopViewController(root: tabVC.selectedViewController) + + } else if root != nil, let presentedVC = root!.presentedViewController { + return presentedVC + + } else { + return root + } + } + + let window = UIApplication.shared.delegate?.window + return getTopViewController(root: window??.rootViewController) +} + +func cpk_commonAncestorFor(item1: UIView?, item2: UIView?) -> UIView? { + if let view1 = item1 { + if let view2 = item2 { + + var view: UIView? = view1 + while view != nil && !view2.isDescendant(of: view!) { + view = view?.superview + } + + return view + + } else { + return item1 + } + + } else { + return item2 + } +} + +func cpk_updatePadding(_ padding: [CGFloat], forView view: UIView) { + if let button = view as? UIButton { + var insets = cpk_edgeInsetsFromArray(padding) + button.cpkInsets = insets + let halfGap = (button.cpkGap ?? 0) / 2 + + insets.left += halfGap + insets.right += halfGap + button.contentEdgeInsets = insets + + } else if let textField = view as? UITextField { + textField.cpkPadding = cpk_edgeInsetsFromArray(padding) + + } else if let textView = view as? UITextView { + textView.textContainer.lineFragmentPadding = 0 + textView.textContainerInset = cpk_edgeInsetsFromArray(padding) + } +} + +func cpk_pinOptions(_ options: [CPKViewPinOptions], forView view: UIView) { + view.makeCons({ + var values = [CGFloat]() + + for option in options { + switch option { + case let .x(offset): $0.left.equal(offset) + case let .y(offset): $0.top.equal(offset) + case let .w(value): $0.width.equal(value) + case let .h(value): $0.height.equal(value) + + case let .xy(v1, v2): $0.left.top.equal(v1, v2); view.frame.origin = CGPoint(x: v1, y: v2) + case let .wh(v1, v2): $0.width.height.equal(v1, v2); view.frame.size = CGSize(width: v1, height: v2) + case let .xywh(v1, v2, v3, v4): $0.left.top.width.height.equal(v1, v2, v3, v4) + + case let .maxX(offset): $0.right.equal(offset) + case let .maxY(offset): $0.bottom.equal(offset) + case let .maxXY(v1, v2): $0.right.bottom.equal(v1, v2) + + case let .centerX(offset): $0.centerX.equal(offset) + case let .centerY(offset): $0.centerY.equal(offset) + case let .centerXY(v1, v2): $0.center.equal(v1, v2) + + case let .whRatio(ratio): $0.width.equal(view).height.multiply(ratio) + + case .center: $0.center.equal(0) + case .ratio: $0.width.equal(view).height.multiply(view.frame.width / view.frame.height) + + case let .hhp(value): view.setContentHuggingPriority(value, for: .horizontal) + case let .vhp(value): view.setContentHuggingPriority(value, for: .vertical) + case let .hrp(value): view.setContentCompressionResistancePriority(value, for: .horizontal) + case let .vrp(value): view.setContentCompressionResistancePriority(value, for: .vertical) + + case .lowHugging: + view.setContentHuggingPriority(kLowPriority, for: .horizontal) + view.setContentHuggingPriority(kLowPriority, for: .vertical) + + case .lowResistance: + view.setContentCompressionResistancePriority(kLowPriority, for: .horizontal) + view.setContentCompressionResistancePriority(kLowPriority, for: .vertical) + + case let .___value(value): values.append(value) + } + } + + if values.count == 1 { + $0.height.equal(values[0]) + } else if values.count == 2 { + $0.size.equal(values[0], values[1]) + } else if values.count == 4 { + $0.origin.size.equal(values[0], values[1], values[2], values[3]) + } + }) +} + +func cpk_canActivateConstraint(_ constraint: NSLayoutConstraint) -> Bool { + return cpk_commonAncestorFor(item1: constraint.firstItem as? UIView, + item2: constraint.secondItem as? UIView) != nil +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +//MARK: Private extensions + +fileprivate var cpkObjectAssociatedObject: UInt8 = 0 + +let CPKLabelLinkAttributeName = "CPKLabelLink" +let CPKLabelLinkAttributeValue = "CPKLabelLinkValue" + +fileprivate let CPKLinkColor = UIColor(red: 0, green: 0.478431, blue: 1, alpha: 1) + +fileprivate let CPKFixLineSpacingIssueAttributeName = "CPKFixLineSpacingIssue" +fileprivate let CPKFixLineSpacingIssueAttributeValue = "Fix single line Label with lineSpacing issue" + + +public extension NSObject { + + @discardableResult + public static func cpk_swizzle(method1: Any, method2: Any) -> Bool { + var s1 = method1 as? Selector + var s2 = method2 as? Selector + + if s1 == nil, let name = method1 as? String { + s1 = NSSelectorFromString(name) + } + + if s2 == nil, let name = method2 as? String { + s2 = NSSelectorFromString(name) + } + + if s1 == nil || s2 == nil { + return false + } + + var m1 = class_getInstanceMethod(self, s1) + var m2 = class_getInstanceMethod(self, s2) + + if m1 == nil || m2 == nil { + return false + } + + class_addMethod(self, s1, method_getImplementation(m1), method_getTypeEncoding(m1)) + class_addMethod(self, s2, method_getImplementation(m2), method_getTypeEncoding(m2)) + + m1 = class_getInstanceMethod(self, s1) + m2 = class_getInstanceMethod(self, s2) + method_exchangeImplementations(m1, m2) + + return true + } + + @discardableResult + public static func cpk_swizzleClass(method1: Any, method2: Any) -> Bool { + return object_getClass(self).cpk_swizzle(method1: method1, method2: method2) + } + + @discardableResult + public func cpk_safePerform(selector: Selector) -> Any? { + if self.responds(to: selector) { + return self.perform(selector).takeRetainedValue() + } else { + return nil + } + } + + @discardableResult + public static func cpk_safePerform(selector: Selector) -> Any? { + if self.responds(to: selector) { + return self.perform(selector).takeRetainedValue() + } else { + return nil + } + } + + public func cpk_associatedObjectFor(key: String) -> Any? { + if let dict = objc_getAssociatedObject(self, &cpkObjectAssociatedObject) as? NSMutableDictionary { + return dict[key] + } else { + return nil + } + } + + public func cpk_setAssociated(object: Any?, forKey key: String) { + var dict = objc_getAssociatedObject(self, &cpkObjectAssociatedObject) as? NSMutableDictionary + + if dict == nil { + dict = NSMutableDictionary() + objc_setAssociatedObject(self, &cpkObjectAssociatedObject, dict, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + + dict![key] = object + } +} + + +extension NSMutableAttributedString { + + private var cpkSelectedRanges: NSMutableIndexSet { + get { + var indexSet = cpk_associatedObjectFor(key: #function) as? NSMutableIndexSet + if indexSet == nil { + indexSet = NSMutableIndexSet() + cpk_setAssociated(object: indexSet, forKey: #function) + } + return indexSet! + } + set { cpk_setAssociated(object: newValue, forKey: #function) } + } + + private var cpkIsJustSelectingRange: Bool { + get { return (cpk_associatedObjectFor(key: #function) as? Bool) ?? false } + set { cpk_setAssociated(object: newValue, forKey: #function) } + } + + var cpkPreventOverrideAttribute: Bool { + get { return (cpk_associatedObjectFor(key: #function) as? Bool) ?? false } + set { cpk_setAssociated(object: newValue, forKey: #function) } + } + + func cpk_select(range: NSRange?, setFlag: Bool = true) { + if !self.cpkIsJustSelectingRange { + self.cpkSelectedRanges.removeAllIndexes() + } + + if range != nil { + self.cpkSelectedRanges.add(in: range!) + } + + if setFlag { + self.cpkIsJustSelectingRange = true + } + } + + func cpk_addAttribute(name: String, value: Any) { + self.cpkIsJustSelectingRange = false + + self.cpkSelectedRanges.enumerateRanges({ (range, stop) in + + if name == CPKLabelLinkAttributeName { + addAttribute(name, value: value, range: range) + addAttribute(NSForegroundColorAttributeName, value: CPKLinkColor, range: range) + + } else { + if self.cpkPreventOverrideAttribute { + cpk_addAttributeIfNotExisted(name: name, value: value, range: range) + } else { + addAttribute(name, value: value, range: range) + } + } + }); + } + + func cpk_addAttributeIfNotExisted(name: String, value: Any, range: NSRange) { + let indexSets = NSMutableIndexSet(indexesIn: range) + enumerateAttribute(name, in: range, options: NSAttributedString.EnumerationOptions(rawValue: 0)) { (value, range, stop) in + if value != nil { + indexSets.remove(in: range) + } + } + + indexSets.enumerateRanges({ (range, stop) in + addAttribute(name, value: value, range: range) + }) + } + + func cpk_addParagraphAttribute(key: String, value: Any, range: NSRange? = nil) { + let targetRange = range ?? NSMakeRange(0, self.length) + var mutableStyle: NSMutableParagraphStyle + + if let paraStyle = attribute(NSParagraphStyleAttributeName, + at: targetRange.location, + longestEffectiveRange: nil, + in: targetRange) as? NSParagraphStyle { + + mutableStyle = paraStyle.mutableCopy() as! NSMutableParagraphStyle + + } else { + mutableStyle = NSMutableParagraphStyle() + mutableStyle.lineBreakMode = .byTruncatingTail + } + + mutableStyle.setValue(value, forKey: key) + addAttribute(NSParagraphStyleAttributeName, value: mutableStyle, range: targetRange) + + if key == "lineSpacing" { + addAttribute(CPKFixLineSpacingIssueAttributeName, value: CPKFixLineSpacingIssueAttributeValue, range: targetRange) + } + } +} + + +extension UIView { + + var cpkAutoRoundingRadius: Bool { + get { return cpk_associatedObjectFor(key: #function) as? Bool ?? false } + set { cpk_setAssociated(object: newValue, forKey: #function) } + } + + var cpkTouchInsets: UIEdgeInsets? { + get { return cpk_associatedObjectFor(key: #function) as? UIEdgeInsets } + set { cpk_setAssociated(object: newValue, forKey: #function) } + } + + override open class func initialize() { + if self == UIView.self { + cpk_swizzle(method1: "setBounds:", method2: #selector(UIView.cpk_setBounds)) + cpk_swizzle(method1: #selector(UIView.point(inside:with:)), method2: #selector(UIView.cpk_point(inside:with:))) + } + } + + func cpk_setBounds(_ frame: CGRect) { + cpk_setBounds(frame) + + if self.cpkAutoRoundingRadius { + self.layer.cornerRadius = self.bounds.height / 2 + cpk_masksToBoundsIfNeed() + } + } + + func cpk_point(inside point: CGPoint, with event: UIEvent?) -> Bool { + if let insets = self.cpkTouchInsets { + let rect = UIEdgeInsetsInsetRect(self.bounds, insets) + return rect.contains(point) + } else { + return self.cpk_point(inside: point, with: event) + } + } + + func cpk_masksToBoundsIfNeed() { + self.layer.masksToBounds = false + } + + func cpk_onClick(_ closureOrTarget: Any, _ action: Any? = nil) { + func cpk_onClickInner(target: Any, action: Any ) { + var sel = action as? Selector + if sel == nil, let methodName = action as? String { + sel = NSSelectorFromString(methodName) + } + + if let button = (self as Any) as? UIButton { + button.addTarget(target, action: sel!, for: .touchUpInside) + } else { + let tap = UITapGestureRecognizer.init(target: target, action: sel!) + self.addGestureRecognizer(tap) + } + } + + isUserInteractionEnabled = true + + if let action = action { + cpk_onClickInner(target: closureOrTarget, action: action) + } else { + cpk_onClickInner(target: self, action: #selector(UIView.cpk_onClickHandler)) + cpk_setAssociated(object: closureOrTarget, forKey: "cpk_OnClick") + } + } + + func cpk_onClickHandler() { + let callback = cpk_associatedObjectFor(key: "cpk_OnClick") + + if let closure = callback as? ()->() { + closure() + } else if let closure = callback as? ()->(Any) { + let _ = closure() + } else if let closure = callback as? (UIView)->() { + closure(self) + } else if let closure = callback as? ((UILabel)->()), let view = (self as Any) as? UILabel { + closure(view) + } else if let closure = callback as? ((UIImageView)->()), let view = (self as Any) as? UIImageView { + closure(view) + } else if let closure = callback as? ((UIButton)->()), let view = (self as Any) as? UIButton { + closure(view) + } else if let closure = callback as? ((UITextField)->()), let view = (self as Any) as? UITextField { + closure(view) + } else if let closure = callback as? ((UITextView)->()), let view = (self as Any) as? UITextView { + closure(view) + } else if let closure = callback as? ((CPKStackView)->()), let view = (self as Any) as? CPKStackView { + closure(view) + } else if let closure = callback as? ((Any)->()) { + closure(self) + } + } + + func cpk_generateCallbackClosure(_ closureOrTarget: Any, _ action: Any? = nil) -> Any? { + if action == nil { + return closureOrTarget + + } else { + if let target = closureOrTarget as? NSObject { + weak var weakTarget = target + weak var weakSelf = self + let sel = Sel(action!) + + return { + let _ = weakTarget?.perform(sel, with: weakSelf) + return + } + + } else { + return nil + } + } + } +} + + +extension UILabel { + override func cpk_masksToBoundsIfNeed() { + self.layer.masksToBounds = self.layer.cornerRadius > 0 + } +} + + +extension UIImageView { + override func cpk_masksToBoundsIfNeed() { + self.layer.masksToBounds = self.layer.cornerRadius > 0 && self.image != nil + } +} + + +extension UIButton { + var cpkGap: CGFloat? { + get { return cpk_associatedObjectFor(key: #function) as? CGFloat } + set { cpk_setAssociated(object: newValue, forKey: #function) } + } + + var cpkInsets: UIEdgeInsets? { + get { return cpk_associatedObjectFor(key: #function) as? UIEdgeInsets } + set { cpk_setAssociated(object: newValue, forKey: #function) } + } + + override func cpk_masksToBoundsIfNeed() { + let img = image(for: .normal) + let highImg = image(for: .highlighted) + let bgImg = backgroundImage(for: .normal) + let highBgImg = backgroundImage(for: .highlighted) + + let hasImage = (img != nil || highImg != nil || bgImg != nil || highBgImg != nil) + self.layer.masksToBounds = (self.layer.cornerRadius > 0 && hasImage) + } +} + + +extension UITextField { + var cpkMaxLength: Int { + get { return cpk_associatedObjectFor(key: #function) as? Int ?? 0 } + set { cpk_setAssociated(object: newValue, forKey: #function); cpk_watchTextChange() } + } + + var cpkPadding: UIEdgeInsets { + get { return cpk_associatedObjectFor(key: #function) as? UIEdgeInsets ?? UIEdgeInsetsMake(0, 0, 0, 0) } + set { cpk_setAssociated(object: newValue, forKey: #function) } + } + + var cpkTextChangedClosure: Any? { + get { return cpk_associatedObjectFor(key: #function) } + set { cpk_setAssociated(object: newValue, forKey: #function); cpk_watchTextChange() } + } + + var cpkDidEndOnExistClosure: Any? { + get { return cpk_associatedObjectFor(key: #function) } + set { cpk_setAssociated(object: newValue, forKey: #function); cpk_watchOnEndEvent() } + } + + override open class func initialize() { + if self == UITextField.self { + cpk_swizzle(method1: #selector(UITextField.textRect(forBounds:)), + method2: #selector(UITextField.cpk_textRect(forBounds:))) + + cpk_swizzle(method1: #selector(UITextField.editingRect(forBounds:)), + method2: #selector(UITextField.cpk_editingRect(forBounds:))) + } + } + + public func cpk_textRect(forBounds bounds: CGRect) -> CGRect { + let rect = self.cpk_textRect(forBounds: bounds) + return UIEdgeInsetsInsetRect(rect, self.cpkPadding) + } + + public func cpk_editingRect(forBounds bounds: CGRect) -> CGRect { + let rect = cpk_editingRect(forBounds: bounds) + return UIEdgeInsetsInsetRect(rect, self.cpkPadding) + } + + private func cpk_watchTextChange() { + let sel = #selector(cpk_textDidChange) + removeTarget(self, action: sel, for: .editingChanged) + addTarget(self, action: sel, for: .editingChanged) + } + + private func cpk_watchOnEndEvent() { + let sel = #selector(cpk_didEndOnExit) + removeTarget(self, action: sel, for: .editingDidEndOnExit) + addTarget(self, action: sel, for: .editingDidEndOnExit) + } + + func cpk_textDidChange() { + let hasMarked = cpk_limitTextInput(self, maxLength: self.cpkMaxLength) + if !hasMarked { + if let closure = self.cpkTextChangedClosure as? (UITextField)->() { + closure(self) + } + } + } + + func cpk_didEndOnExit() { + if let closure = self.cpkDidEndOnExistClosure as? (UITextField)->() { + closure(self) + } + } +} + + +extension AttStrSelectionOptions : ExpressibleByStringLiteral { + public init(unicodeScalarLiteral value: String) { + self = .match(value) + } + + public init(extendedGraphemeClusterLiteral value: String) { + self = .match(value) + } + + public init(stringLiteral value: String) { + self = .match(value) + } +} + + +extension CPKViewPinOptions : ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral { + public init(integerLiteral value: Int) { + self = .___value(CGFloat(value)) + } + + public init(floatLiteral value: Float) { + self = .___value(CGFloat(value)) + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +//MARK: AlertMaker + +public class AlertMaker { + var cpkTitle: Any? + var cpkMessage: Any? + var cpkTint: Any? + var cpkStyle: UIAlertControllerStyle + + private var actions = [UIAlertAction]() + + init(style: UIAlertControllerStyle) { + self.cpkStyle = style + } + + func cpk_addAction(style: UIAlertActionStyle, title: Any, handler: (()->())? ) { + var titleString: String? + var titleColor: UIColor? + + if let attTitle = title as? NSAttributedString { + titleString = attTitle.string + titleColor = attTitle.attribute(NSForegroundColorAttributeName, at: 0, effectiveRange: nil) as? UIColor + } else { + titleString = String(describing: title) + } + + let action = UIAlertAction(title: titleString, style: style) { (action) in + if let callback = handler { + callback() + } + } + + if titleColor != nil { + action.setValue(titleColor!, forKey: "titleTextColor") + } + + actions.append(action) + } + + func cpk_present(_ inside: UIViewController?) { + let alert = UIAlertController(title: nil, message: nil, preferredStyle: self.cpkStyle) + alert.view.tintColor = Color(self.cpkTint) + + if let attTitle = self.cpkTitle as? NSAttributedString { + alert.setValue(attTitle, forKey: "attributedTitle") + } else if let title = self.cpkTitle { + alert.title = String(describing: title) + } + + if let attMessage = self.cpkMessage as? NSAttributedString { + alert.setValue(attMessage, forKey: "attributedMessage") + } else if let message = self.cpkMessage { + alert.message = String(describing: message) + } + + for action in self.actions { + alert.addAction(action) + } + + if let vc = (inside ?? cpk_getTopViewController()) { + vc.present(alert, animated: true, completion: nil) + } + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +//MARK: ConsMaker + +public class ConsAtts: NSObject { + @discardableResult + func addAttributes(_ attributes: NSLayoutAttribute...) -> Cons { + //only for override + return Cons(firstItem: UIView()) + } +} + +public class Cons: ConsAtts { + private let firstItem: UIView + private var secondItem: UIView? + + var relation = NSLayoutRelation.equal + + var firstItemAttributes = [NSLayoutAttribute]() + var secondItemAttributes = [NSLayoutAttribute]() + + var multiplierValues = [CGFloat]() + var constantValues = [CGFloat]() + var priorityValues = [UILayoutPriority]() + + var storePointers = [UnsafeMutablePointer]() + + + init(firstItem: UIView) { + self.firstItem = firstItem + self.firstItem.translatesAutoresizingMaskIntoConstraints = false + } + + @discardableResult + override func addAttributes(_ attributes: NSLayoutAttribute...) -> Cons { + for att in attributes { + if secondItem == nil { + firstItemAttributes.append(att) + } else { + secondItemAttributes.append(att) + } + } + return self + } + + func updateSecondItem(_ item2OrValues: [Any]) { + if let item2 = item2OrValues.first as? UIView { + self.secondItem = item2 + + } else if let item2 = item2OrValues.first as? String { + if item2 == "self" { + self.secondItem = self.firstItem + } else { + assert(false, "invalid second item") + } + + } else { + self.secondItem = nil + for any in item2OrValues { + + if let point = any as? CGPoint { + self.constantValues.append(point.x) + self.constantValues.append(point.y) + + } else if let size = any as? CGSize { + self.constantValues.append(size.width) + self.constantValues.append(size.height) + + } else if let rect = any as? CGRect { + self.constantValues.append(rect.origin.x) + self.constantValues.append(rect.origin.y) + self.constantValues.append(rect.size.width) + self.constantValues.append(rect.size.height) + + } else if let insets = any as? UIEdgeInsets { + self.constantValues.append(insets.top) + self.constantValues.append(insets.left) + self.constantValues.append(insets.bottom) + self.constantValues.append(insets.right) + + } else { + self.constantValues.append(CPKFloat(any)) + } + } + } + } + + private func multiplierValue(atIndex index: Int) -> CGFloat { + var multiplier: CGFloat = 1 + + if index < multiplierValues.count { + multiplier = multiplierValues[index] + } else if let last = multiplierValues.last { + multiplier = last + } + + return multiplier != 0 ? multiplier : 1 + } + + private func constantValue(atIndex index: Int) -> CGFloat { + var constant: CGFloat = 0 + + if index < constantValues.count { + constant = constantValues[index] + } else if let last = constantValues.last { + constant = last + } + + return constant + } + + private func priorityValue(atIndex index: Int) -> UILayoutPriority { + var priority: UILayoutPriority = UILayoutPriorityRequired + + if index < priorityValues.count { + priority = priorityValues[index] + } else if let last = priorityValues.last { + priority = last + } + + return priority + } + + private func secondItemValue(att1: NSLayoutAttribute) -> UIView? { + var secondItem = self.secondItem + if (secondItem == nil && att1 != .width && att1 != .height) { + secondItem = self.firstItem.superview + } + return secondItem + } + + fileprivate func makeConstraints() -> [NSLayoutConstraint] { + var layoutConstraints = [NSLayoutConstraint]() + + for i in 0.. Bool { + + for att in self.firstItemAttributes { + if !((att == .width || att == .height) && self.secondItem == nil) { + if self.firstItem.superview == nil { + return true + } + } + } + + return false + } +} + + +public class ConsMaker: ConsAtts { + private let firstItem: UIView + private var cons = [Cons]() + + init(firstItem: UIView) { + self.firstItem = firstItem + } + + @discardableResult + override func addAttributes(_ attributes: NSLayoutAttribute...) -> Cons { + let c = Cons(firstItem: firstItem) + cons.append(c) + + for att in attributes { + c.addAttributes(att) + } + return c + } + + private func activiteConstraints(_ constraints: [NSLayoutConstraint]) { + NSLayoutConstraint.activate(constraints) + self.firstItem.cpk_addInstalledConstraints(constraints) + } + + private func shouldDelayMakingConstraints() -> Bool { + for c in cons { + if c.shouldDelayMakingConstraints() { + return true + } + } + return false + } + + func remakeConstraints() { + self.firstItem.cpk_removeAllInstalledConstraints() + + func makeConstraintsPrivate() { + var layoutConstraints = [NSLayoutConstraint]() + for c in cons { + let constraints = c.makeConstraints() + layoutConstraints.append(contentsOf: constraints) + + for (index, constraint) in constraints.enumerated() { + if index < c.storePointers.count { + let pointer = c.storePointers[index] + pointer.pointee = constraint + } + } + } + activiteConstraints(layoutConstraints) + } + + if shouldDelayMakingConstraints() { + DelayedConstraints.addOperation(makeConstraintsPrivate) + } else { + makeConstraintsPrivate() + } + } + + func updateConstraints() { + + func updateConstraintsPrivate() { + var newConstraints = [NSLayoutConstraint]() + + for c in cons { + let layoutConstraints = c.makeConstraints() + + for (index, constraint) in layoutConstraints.enumerated() { + var target = constraint + + if let old = self.firstItem.cpk_similarInstalledConstraint(constraint) { + old.constant = constraint.constant + target = old + + } else { + newConstraints.append(constraint) + } + + if index < c.storePointers.count { + let pointer = c.storePointers[index] + pointer.pointee = target + } + } + } + + activiteConstraints(newConstraints) + } + + if shouldDelayMakingConstraints() { + DelayedConstraints.addOperation(updateConstraintsPrivate) + } else { + updateConstraintsPrivate() + } + } +} + + +fileprivate extension UIView { + var cpkInstalledConstraints: NSHashTable { + var hash = cpk_associatedObjectFor(key: "cpkInstalledConstraints") as? NSHashTable + if (hash == nil) { + hash = NSHashTable.weakObjects() + cpk_setAssociated(object: hash, forKey: "cpkInstalledConstraints") + } + return hash! + } + + func cpk_similarInstalledConstraint(_ constraint: NSLayoutConstraint) -> NSLayoutConstraint? { + for target in self.cpkInstalledConstraints.allObjects { + if target.cpk_isSimilarTo(constraint: constraint) { + return target + } + } + return nil; + } + + func cpk_addInstalledConstraints(_ constraints: [NSLayoutConstraint]) { + let hash = self.cpkInstalledConstraints + for constraint in constraints { + hash.add(constraint) + } + } + + func cpk_removeAllInstalledConstraints() { + NSLayoutConstraint.deactivate(self.cpkInstalledConstraints.allObjects) + self.cpkInstalledConstraints.removeAllObjects() + } +} + +fileprivate extension NSLayoutConstraint { + func cpk_isSimilarTo(constraint: NSLayoutConstraint) -> Bool { + return self.firstItem === constraint.firstItem && + self.secondItem === constraint.secondItem && + self.firstAttribute == constraint.firstAttribute && + self.secondAttribute == constraint.secondAttribute && + self.relation == constraint.relation && + self.priority == constraint.priority && + self.multiplier == constraint.multiplier + } +} + +fileprivate class DelayedConstraints { + private static var delayedOperations = [()->()]() + private static var isWaiting = false + + class func addOperation(_ operation: @escaping ()->()) { + + delayedOperations.append(operation) + + if !isWaiting { + isWaiting = true + + CFRunLoopPerformBlock(CFRunLoopGetMain(), CFRunLoopMode.commonModes.rawValue, { + for o in delayedOperations { o() } + delayedOperations.removeAll() + isWaiting = false + }) + } + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +/** + * StylesMaker + */ +let CPKStylesBorderWidth = "CPKBorderWidth" +let CPKStyleBOrderColor = "CPKBorderColor" + +public class StylesMaker: NSObject { + static var globalStyles = Dictionary() + + private var styles = Dictionary() + + @discardableResult func addStyle(key: String, value: Any) -> Self { + styles[key] = value + return self + } + + func applyTo(view: UIView) { + for (key, value) in styles { + + if key == "border" { + if let dict = value as? Dictionary { + view.border(CPKFloat(dict["borderWidth"]), dict["borderColor"]) + } + + } else if key == "shadow" { + if let dict = value as? Dictionary { + view.shadow(dict["opacity"]!, dict["radius"]!, dict["offsetX"]!, dict["offsetY"]!) + } + + } else if key == "pin" { + cpk_pinOptions(value as! [CPKViewPinOptions], forView: view) + + } else if key == "align" { + if let stack = view as? CPKStackView { + stack.align(value as! CPKStackAlignment) + } else if let label = view as? UILabel { + label.align(value as! NSTextAlignment) + } else if let textField = view as? UITextField { + textField.align(value as! NSTextAlignment) + } else if let textView = view as? UITextView { + textView.align(value as! NSTextAlignment) + } + + } else if key == "mode" { + if let imageView = view as? UIImageView { + imageView.mode(value as! UIViewContentMode) + } + + } else if key == "reversed" { + if let button = view as? UIButton { + button.reversed(value as! Bool) + } + + } else if key == "padding" { + cpk_updatePadding(value as! [CGFloat], forView: view) + } + + else if key == "secure" { + if let textField = view as? UITextField { + textField.secure(value as! Bool) + } + } + + else if key == "keyboard" { + if let textField = view as? UITextField { + textField.keyboard(value as! UIKeyboardType) + } + } + + else if key == "returnKey" { + if let textField = view as? UITextField { + textField.returnKey(value as! UIReturnKeyType) + } + } + + else if key == "clearMode" { + if let textField = view as? UITextField { + textField.clearMode(value as! UITextFieldViewMode) + } + } + + else { + let sel = NSSelectorFromString(key + ":") + if view.responds(to: sel) { + view.perform(sel, with: value) + } + } + } + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +//MARK: StaticTableView + +public enum CPKTableViewCellAccessoryType { + case none + case disclosureIndicator + case detailDisclosureButton + case checkmark + case detailButton + case view(UIView) //AccessoryView +} + +public class StaticTableView: UITableView, UITableViewDelegate, UITableViewDataSource { + public var checkedIndexPaths: [IndexPath] { + var indexPaths = [IndexPath]() + + for section in sections { + for row in section.rows { + if row.accessoryType != nil && row.accessoryType! == .checkmark { + indexPaths.append(row.indexPath) + } + } + } + + return indexPaths + } + + public func update(detail: String, at indexPath : IndexPath) { + let row = rowAt(indexPath: indexPath) + row.detail(detail) + } + + + fileprivate var sections = [StaticSection]() + + var autoEnableScroll = true + + var cellHeight: CGFloat? + var separatorIndent: CGFloat? + + var accessoryType: CPKTableViewCellAccessoryType? + var customHandler: ((StaticRow)->())? + + var onClickHandler: ((StaticRow)->())? + + var textFont: Any? + var textColor: Any? + var detailFont: Any? + var detailColor: Any? + + public init(sectionsOrRows: [Any], style: UITableViewStyle) { + super.init(frame: CGRect.zero, style: style) + self.estimatedRowHeight = 44 + self.delegate = self + self.dataSource = self + updateSections(sectionsOrRows: sectionsOrRows) + + addObserver(self, forKeyPath: "contentSize", options: .new, context: nil) + addObserver(self, forKeyPath: "frame", options: .new, context: nil) + } + + public override func observeValue(forKeyPath keyPath: String?, + of object: Any?, + change: [NSKeyValueChangeKey : Any]?, + context: UnsafeMutableRawPointer?) { + + if keyPath == "contentSize" { + invalidateIntrinsicContentSize() + + } + + if self.autoEnableScroll { + self.isScrollEnabled = !(abs(self.frame.size.height - visibleHeight()) < 0.1) + } + } + + public required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + removeObserver(self, forKeyPath: "contentSize") + removeObserver(self, forKeyPath: "frame") + } + + private func sectionsFrom(sectionsOrRows: [Any]) -> [StaticSection] { + var sections = [StaticSection]() + var rows: [StaticRow]? = nil + + for any in sectionsOrRows { + + if let array = any as? Array { + let result = sectionsFrom(sectionsOrRows: array) + sections.append(contentsOf: result) + + } else if let section = any as? StaticSection { + if rows != nil { + sections.append(StaticSection(rowsOrStrings: rows!)) + rows = nil + } + sections.append(section) + + } else if let row = any as? StaticRow { + if rows == nil { rows = [StaticRow]() } + rows?.append(row) + + } else if let text = any as? String { + if rows == nil { rows = [StaticRow]() } + rows?.append(Row.str(text)) + } + } + + if rows != nil { + sections.append(StaticSection(rowsOrStrings: rows!)) + } + + return sections + } + + private func updateSections(sectionsOrRows: [Any]) { + self.sections = sectionsFrom(sectionsOrRows: sectionsOrRows) + + for s in self.sections { + s.table = self + } + + // if self.style == .grouped && sections.count == 1 { + // let section = sections.first + // if section?.headerValue == nil && section?.footerValue == nil { + // section?.header(0).footer(0) + // } + // } + } + + private func visibleHeight() -> CGFloat { + return contentSize.height + contentInset.top + contentInset.bottom + } + + private func rowAt(indexPath: IndexPath) -> StaticRow { + return sections[indexPath.section].rows[indexPath.row] + } + + public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + let row = rowAt(indexPath: indexPath) + return row.rowHeight() + } + + public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let row = rowAt(indexPath: indexPath) + let cell = row.getCell(indexPath: indexPath) + + if let callback = row.customHandler { + callback(row) + } + + if let callback = row.section.table.customHandler { + callback(row) + } + + return cell + } + + public func numberOfSections(in tableView: UITableView) -> Int { + return sections.count + } + + public func tableView(_ tableView: UITableView, numberOfRowsInSection sectionIndex: Int) -> Int { + return sections[sectionIndex].rows.count + } + + public func tableView(_ tableView: UITableView, heightForHeaderInSection sectionIndex: Int) -> CGFloat { + let section = sections[sectionIndex] + + if let view = section.headerValue as? UIView { + return view.bounds.height + + } else if let height = CPKFloatOptional(section.headerValue) { + return height == 0 ? 0.001 : height + } + + return UITableViewAutomaticDimension + } + + public func tableView(_ tableView: UITableView, heightForFooterInSection sectionIndex: Int) -> CGFloat { + let section = sections[sectionIndex] + + if let view = section.footerValue as? UIView { + return view.bounds.height + + } else if let height = CPKFloatOptional(section.footerValue) { + return height == 0 ? 0.001 : height + } + + return UITableViewAutomaticDimension + } + + public func tableView(_ tableView: UITableView, titleForHeaderInSection sectionIndex: Int) -> String? { + let section = sections[sectionIndex] + + if let title = section.headerValue as? String { + return title + } else { + return nil + } + } + + public func tableView(_ tableView: UITableView, titleForFooterInSection sectionIndex: Int) -> String? { + let section = sections[sectionIndex] + + if let footer = section.footerValue as? String { + return footer + } else { + return nil + } + } + + public func tableView(_ tableView: UITableView, viewForHeaderInSection sectionIndex: Int) -> UIView? { + let section = sections[sectionIndex] + + if let view = section.headerValue as? UIView { + return view + } else { + return nil + } + } + + public func tableView(_ tableView: UITableView, viewForFooterInSection sectionIndex: Int) -> UIView? { + let section = sections[sectionIndex] + + if let view = section.footerValue as? UIView { + return view + } else { + return nil + } + } + + public func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? { + let row = rowAt(indexPath: indexPath) + return row.allowSelection() ? indexPath : nil + } + + public func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool { + let row = rowAt(indexPath: indexPath) + return row.allowSelection() + } + + public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + + let section = sections[indexPath.section] + let row = rowAt(indexPath: indexPath) + + if section.enableSingleCheck != nil { + for r in section.rows { r.check(false) } + row.check(true) + + } else if section.enableMultiCheck != nil { + if row.accessoryType != nil && row.accessoryType! == .checkmark { + row.check(false) + } else { + row.check(true) + } + } + + if let callback = row.onClickHandler { + callback(row) + } + + if let callback = section.table.onClickHandler { + callback(row) + } + } + + public func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) { + let row = rowAt(indexPath: indexPath) + if let callback = row.onButtonHandler { + callback(row) + } + } + + public override var intrinsicContentSize: CGSize { + return CGSize(width: -1, height: visibleHeight()) + } +} + +public class StaticSection: NSObject { + fileprivate var rows = [StaticRow]() + fileprivate weak var table: StaticTableView! + + var enableSingleCheck: Bool? + var enableMultiCheck: Bool? + + var checkedImage: UIImage? + var uncheckedImage: UIImage? + + var headerValue: Any? + var footerValue: Any? + + + fileprivate func allowChecking() -> Bool { + return enableSingleCheck != nil || enableMultiCheck != nil + } + + private func rowsFrom(rowsOrStrings: [Any]) -> [StaticRow] { + var rows = [StaticRow]() + + for any in rowsOrStrings { + if let row = any as? StaticRow { + rows.append(row) + } + + if let text = any as? String { + rows.append(Row.str(text)) + } + + if let array = any as? Array { + let result = rowsFrom(rowsOrStrings: array) + rows.append(contentsOf: result) + } + } + + return rows + } + + + init(rowsOrStrings: [Any]) { + super.init() + self.rows = rowsFrom(rowsOrStrings: rowsOrStrings) + for row in self.rows { + row.section = self + } + } +} + + +public class StaticRow: UIView { + public fileprivate(set) var cell: UITableViewCell! + public fileprivate(set) var indexPath: IndexPath! + public internal(set) var switchView: UISwitch! { + didSet { + switchView?.addTarget(self, action: #selector(switchDidChange), for: .valueChanged) + } + } + + fileprivate weak var section: StaticSection! + + var cellHeight: CGFloat? + var separatorIndent: CGFloat? + + var cellStyle: UITableViewCellStyle = .default + + var onClickHandler: ((StaticRow)->())? + var onButtonHandler: ((StaticRow)->())? + var onChangeHandler: ((StaticRow)->())? + var customHandler: ((StaticRow)->())? + + var accessoryType: CPKTableViewCellAccessoryType? { + didSet { + updateCell() + } + } + + var text: Any? { + didSet { updateCell() } + } + + var detailText: Any? { + didSet { updateCell() } + } + + var image: UIImage? { + didSet { updateCell() } + } + + public override class var layerClass: Swift.AnyClass { + return CATransformLayer.self + } + + func switchDidChange() { + if let callback = onChangeHandler { + callback(self) + } + } + + private func updateCell() { + + if cell == nil { + return + } + + if let image = self.image { + cell.imageView?.image = image + } + + if let text = self.text { + cell.textLabel?.str(text) + + if !(text is NSAttributedString) { + if let font = self.section.table.textFont { + cell.textLabel?.font(font) + } + + if let color = self.section.table.textColor { + cell.textLabel?.color(color) + } + } + } + + if let detail = self.detailText { + cell.detailTextLabel?.str(detail) + + if !(detail is NSAttributedString) { + if let font = self.section.table.detailFont { + cell.detailTextLabel?.font(font) + } + if let color = self.section.table.detailColor { + cell.detailTextLabel?.color(color) + } + } + } + + if let accType = self.accessoryType ?? self.section.table.accessoryType { + let accs = accType.accessoryType() + cell.accessoryType = accs.0 + + if let view = accs.1 { + cell.accessoryView = view + } + } + + if let section = self.section { + if section.allowChecking() { + let accType = self.accessoryType + + if accType != nil && accType! == .checkmark { + if let onImage = section.checkedImage { + self.cell.accessoryView = ImageView.img(onImage) + } else { + self.cell.accessoryType = .checkmark + self.cell.accessoryView = nil + } + + } else { + if let offImage = section.uncheckedImage { + self.cell.accessoryView = ImageView.img(offImage) + } else { + self.cell.accessoryType = .none + self.cell.accessoryView = nil + } + } + } + } + + if let indent = lineIndent() { + cell.preservesSuperviewLayoutMargins = false + cell.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0) + cell.separatorInset = UIEdgeInsetsMake(0, indent, 0, 0) + } + } + + fileprivate func rowHeight() -> CGFloat { + var rowHeight: CGFloat = 44 + + if let height = self.cellHeight { + rowHeight = height + } else if let height = self.section.table.cellHeight { + rowHeight = height + } + + return rowHeight >= 0 ? rowHeight : UITableViewAutomaticDimension + } + + fileprivate func lineIndent() -> CGFloat? { + if self.separatorIndent != nil { + return self.separatorIndent + } else { + return self.section.table.separatorIndent + } + } + + fileprivate func allowSelection() -> Bool { + if section.allowChecking() { + return true + } + + if self.onClickHandler != nil { + return true + } + + if section.table.onClickHandler != nil { + return true + } + + return false + } + + fileprivate func getCell(indexPath: IndexPath) -> UITableViewCell { + if cell == nil { + var style = self.cellStyle + if style == .default && self.detailText != nil { + style = .value1 + } + + cell = UITableViewCell(style: style, reuseIdentifier: "cell") + } + + self.indexPath = indexPath + updateCell() + + return cell + } +} + + +extension CPKTableViewCellAccessoryType { + public func accessoryType() -> (UITableViewCellAccessoryType, UIView?) { + switch self { + case .none: return (.none, nil) + case .disclosureIndicator: return (.disclosureIndicator, nil) + case .detailDisclosureButton: return (.detailDisclosureButton, nil) + case .checkmark: return (.checkmark, nil) + case .detailButton: return (.detailButton, nil) + case let .view(view): return (.none, view) + } + } +} + +func == (lhs: CPKTableViewCellAccessoryType, rhs: CPKTableViewCellAccessoryType) -> Bool { + switch (lhs, rhs) { + case (.none, .none): return true + case (.disclosureIndicator, .disclosureIndicator): return true + case (.detailDisclosureButton, .detailDisclosureButton): return true + case (.checkmark, .checkmark): return true + case (.detailButton, .detailButton): return true + case (.view(let l), .view(let r)): return l == r + default: return false + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +//MARK: UITextView+Placeholder + +fileprivate var cpkTextViewObservingKeys = ["bounds", + "frame", + "font", + "textAlignment", + "textContainerInset", + "textContainer.lineFragmentpadding"] + + +fileprivate class UITextViewPlaceholder: UILabel { + weak var textView: UITextView? + static private var defaultColor: UIColor? + + func defaultPlaceholderColor() -> UIColor { + if let color = UITextViewPlaceholder.defaultColor { + return color + } else { + let textField = UITextField() + textField.placeholder = " " + + var color = textField.value(forKeyPath: "_placeholderLabel.textColor") as? UIColor + if color == nil { + color = UIColor(red: 199/255, green: 199/255, blue: 205/255, alpha: 1) + } + + UITextViewPlaceholder.defaultColor = color + return color! + } + } + + init(textView: UITextView) { + self.textView = textView + super.init(frame: textView.bounds) + + self.numberOfLines = 0 + self.tag = 31415 + self.textColor = defaultPlaceholderColor() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + fileprivate func update() { + if let textView = self.textView { + self.isHidden = !(textView.text.isEmpty && textView.attributedText.string.isEmpty) + + if !self.isHidden { + var font = textView.font + + if font == nil && textView.text.isEmpty { + textView.text = " " + font = textView.font + textView.text = "" + } + + self.font = font + self.textAlignment = textView.textAlignment + + var rect = UIEdgeInsetsInsetRect(textView.bounds, textView.textContainerInset) + rect = rect.insetBy(dx: textView.textContainer.lineFragmentPadding, dy: 0) + rect.size.height = min(rect.height, self.sizeThatFits(CGSize(width: rect.width, height: 0)).height) + self.frame = rect + } + } + } + + fileprivate override func observeValue(forKeyPath keyPath: String?, + of object: Any?, + change: [NSKeyValueChangeKey : Any]?, + context: UnsafeMutableRawPointer?) { + update() + } +} + + +extension UITextView { + var cpkMaxLength: Int { + get { return cpk_associatedObjectFor(key: #function) as? Int ?? 0 } + set { cpk_setAssociated(object: newValue, forKey: #function); cpk_watchTextChange() } + } + + var cpkTextChangedClosure: Any? { + get { return cpk_associatedObjectFor(key: #function) } + set { cpk_setAssociated(object: newValue, forKey: #function); cpk_watchTextChange() } + } + + private var cpkPlaceholderLabel: UITextViewPlaceholder? { + return self.viewWithTag(31415) as? UITextViewPlaceholder + } + + override open class func initialize() { + if self == UITextView.self { + cpk_swizzle(method1: "dealloc", method2: #selector(UITextView.cpk_deinit)) + } + } + + public func cpk_deinit() { + if let label = self.cpkPlaceholderLabel { + for keyPath in cpkTextViewObservingKeys { + self.removeObserver(label, forKeyPath: keyPath) + } + } + + NotificationCenter.default.removeObserver(self) + self.cpk_deinit() + } + + private func cpk_watchTextChange() { + NotificationCenter.default.removeObserver(self, + name: NSNotification.Name.UITextViewTextDidChange, + object: self) + + NotificationCenter.default.addObserver(self, + selector: #selector(cpk_textDidChange), + name: NSNotification.Name.UITextViewTextDidChange, + object: self) + } + + func cpk_textDidChange() { + let hasMarked = cpk_limitTextInput(self, maxLength: self.cpkMaxLength) + if !hasMarked { + if let closure = self.cpkTextChangedClosure as? (UITextView)->() { + closure(self) + } + } + + self.cpkPlaceholderLabel?.update() + } + + func cpk_setPlaceholder(_ any: Any) { + var placeholderLabel = self.cpkPlaceholderLabel + + if placeholderLabel == nil { + let label = UITextViewPlaceholder(textView: self) + self.insertSubview(label, at: 0) + + for keyPath in cpkTextViewObservingKeys { + self.addObserver(label, forKeyPath: keyPath, options: .new, context: nil) + } + + placeholderLabel = label + cpk_watchTextChange() + } + + if let att = any as? NSAttributedString { + placeholderLabel?.attributedText = att + } else { + placeholderLabel?.text = String(describing: any) + } + + placeholderLabel?.update() + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +//MARK: UILabel + Link + +class LinkInfo: NSObject { + var text: String + var range: NSRange + var boundingRects: [CGRect] + + init(text: String, range: NSRange, boundingRects: [CGRect]) { + self.text = text + self.range = range + self.boundingRects = boundingRects + } + + func contains(point: CGPoint) -> Bool { + for rect in boundingRects { + if rect.contains(point) { + return true + } + } + return false + } + + func shouldCancelTouchAt(point: CGPoint) -> Bool { + for rect in boundingRects { + let touchRect = rect.insetBy(dx: -50, dy: -50) + if touchRect.contains(point) { + return false + } + } + return true + } +} + +class LinkGestureRecognizer: UIGestureRecognizer { + override func touchesBegan(_ touches: Set, with event: UIEvent) { + self.state = .began + } + + override func touchesMoved(_ touches: Set, with event: UIEvent) { + self.state = .changed + } + + override func touchesEnded(_ touches: Set, with event: UIEvent) { + self.state = .ended + } + + override func touchesCancelled(_ touches: Set, with event: UIEvent) { + self.state = .cancelled + } + + override func canPrevent(_ preventedGestureRecognizer: UIGestureRecognizer) -> Bool { + return false + } + + override func canBePrevented(by preventingGestureRecognizer: UIGestureRecognizer) -> Bool { + return false + } +} + + +public extension UILabel { + + public var cpkLayoutManager: NSLayoutManager { + var layoutManager: NSLayoutManager! = cpk_associatedObjectFor(key: #function) as? NSLayoutManager + + if layoutManager == nil { + layoutManager = NSLayoutManager() + + let textStorage = NSTextStorage() + textStorage.addLayoutManager(layoutManager) + layoutManager?.textStorage = textStorage + + let textContainer = NSTextContainer(size: CGSize(width: 0, height: 0)) + textContainer.lineFragmentPadding = 0 + textContainer.layoutManager = layoutManager + layoutManager.addTextContainer(textContainer) + + cpk_setAssociated(object: layoutManager, forKey: #function) + cpk_setAssociated(object: textStorage, forKey: "cpkTextStorage") + } + + if let attStr = self.attributedText { + let att = NSMutableAttributedString(attributedString: attStr) + att.select(.all).preventOverride().font(self.font).align(self.textAlignment) + + if self.numberOfLines != 1 && self.lineBreakMode != .byCharWrapping && self.lineBreakMode != .byWordWrapping { + let value = NSNumber(value: NSLineBreakMode.byWordWrapping.rawValue) + att.cpk_addParagraphAttribute(key: "lineBreakMode", value: value) + } + + layoutManager.textStorage?.setAttributedString(att) + + if let textContainer = layoutManager.textContainers.first { + textContainer.maximumNumberOfLines = self.numberOfLines + textContainer.lineBreakMode = self.lineBreakMode + textContainer.size = self.bounds.size + } + + } else { + layoutManager.textStorage?.setAttributedString(NSAttributedString()) + } + + return layoutManager + } + + public var cpkLinkSelectionColor: UIColor? { + get { return cpk_associatedObjectFor(key: #function) as? UIColor } + set { cpk_setAssociated(object: newValue, forKey: #function) } + } + + public var cpkLinkSelectionRadius: CGFloat? { + get { return cpk_associatedObjectFor(key: #function) as? CGFloat } + set { cpk_setAssociated(object: newValue, forKey: #function) } + } +} + + +extension UILabel { + + var cpkLineGap: CGFloat? { + get { return cpk_associatedObjectFor(key: #function) as? CGFloat } + set { cpk_setAssociated(object: newValue, forKey: #function); cpk_updateAttributedString() } + } + + var cpkLinkHandler: Any? { + get { return cpk_associatedObjectFor(key: #function) } + set { + cpk_setAssociated(object: newValue, forKey: #function) + + guard let hasGesture = self.gestureRecognizers?.contains(where: { + return $0 is LinkGestureRecognizer + }), hasGesture == true else { + let gesture = LinkGestureRecognizer(target: self, action: #selector(cpk_handleLinkGesture(_:))) + addGestureRecognizer(gesture) + return + } + } + } + + fileprivate var cpkSelectedLinkInfo: LinkInfo? { + get { return cpk_associatedObjectFor(key: #function) as? LinkInfo } + set { cpk_setAssociated(object: newValue, forKey: #function) } + } + + fileprivate var cpkSelectionLayers: [CALayer] { + get { return (cpk_associatedObjectFor(key: #function) as? [CALayer]) ?? [CALayer]() } + set { cpk_setAssociated(object: newValue, forKey: #function) } + } + + override open static func initialize() { + if self === UILabel.self { + cpk_swizzle(method1: #selector(setter: UILabel.text), method2: #selector(UILabel.ner_setText(_:))) + } + } + + public func ner_setText(_ text: String) { + self.ner_setText(text) + cpk_updateAttributedString() + } + + private func cpk_updateAttributedString() { + if let lineGap = self.cpkLineGap, let attStr = self.attributedText { + if let mutableAtt = attStr.mutableCopy() as? NSMutableAttributedString { + self.attributedText = mutableAtt.lineGap(lineGap) + } + } + } +} + + +extension UILabel { + + func cpk_handleLinkGesture(_ gesture: LinkGestureRecognizer) { + + func cpk_calculateTextYOffset() -> CGFloat { + let layoutManager = self.cpkLayoutManager + + if let textContainer = layoutManager.textContainers.first { + let textRange = layoutManager.glyphRange(for: textContainer) + let textRect = layoutManager.boundingRect(forGlyphRange: textRange, in: textContainer) + + if self.bounds.height > textRect.height { + return (self.bounds.height - textRect.height) / 2 + } + } + + return 0 + } + + func cpk_calculateBoundingRects(range: NSRange, yOffset: CGFloat) -> [CGRect] { + let layoutManager = self.cpkLayoutManager + var boundingRects = [CGRect]() + + if let textContainer = layoutManager.textContainers.first { + let textRect = layoutManager.usedRect(for: textContainer) + let glyphRange = layoutManager.glyphRange(forCharacterRange: range, actualCharacterRange: nil) + + let handler: (CGRect, UnsafeMutablePointer)->() = { (rect, stop) in + var boundingRect = rect + let subGlyphRange = layoutManager.glyphRange(forBoundingRect: rect, in: textContainer) + let subRect = layoutManager.boundingRect(forGlyphRange: subGlyphRange, in: textContainer) + + let lineRect = layoutManager.lineFragmentUsedRect(forGlyphAt: subGlyphRange.location, effectiveRange: nil) + + if subRect.origin.x > boundingRect.origin.x { + let emptyWidth = subRect.origin.x - boundingRect.origin.x + + boundingRect.origin.x = subRect.origin.x + if boundingRect.size.width > emptyWidth { + boundingRect.size.width -= emptyWidth + } + } + + if let ps = layoutManager.textStorage?.attribute(NSParagraphStyleAttributeName, + at: range.location, + longestEffectiveRange: nil, + in: range) as? NSParagraphStyle { + + if ps.lineSpacing > 0 && ceilf(Float(boundingRect.maxY)) != ceilf(Float(textRect.maxY)) { + let height = boundingRect.height - ps.lineSpacing + boundingRect.size.height = CGFloat(ceilf(Float(height))) + } + } + + if boundingRect.maxX > lineRect.maxX { + boundingRect.size.width = lineRect.maxX - boundingRect.minX + } + + boundingRect.origin.y += yOffset + boundingRects.append(boundingRect) + } + + layoutManager.enumerateEnclosingRects(forGlyphRange: glyphRange, + withinSelectedGlyphRange: NSMakeRange(NSNotFound, 0), + in: textContainer, + using:handler) + } + + return boundingRects + } + + func cpk_handleTouchBegin() { + + if let attStr = self.attributedText { + var linkInfos = [LinkInfo]() + let touchPoint = gesture.location(in: self) + let fullRange = NSMakeRange(0, attStr.length) + let yOffset = cpk_calculateTextYOffset() + + let handler: (Any?, NSRange, UnsafeMutablePointer)->() = { (value, range, stop) in + if value != nil { + let text = attStr.string.subAt(range) + let boundingRects = cpk_calculateBoundingRects(range: range, yOffset: yOffset) + let linkInfo = LinkInfo(text: text, range: range, boundingRects: boundingRects) + linkInfos.append(linkInfo) + } + } + + attStr.enumerateAttribute(CPKLabelLinkAttributeName, + in: fullRange, + options: NSAttributedString.EnumerationOptions.init(rawValue: 0), + using: handler) + + for linkInfo in linkInfos { + if linkInfo.contains(point: touchPoint) { + self.cpkSelectedLinkInfo = linkInfo + self.perform(#selector(cpk_addHighlightedLayers(for:)), with: linkInfo, afterDelay: 0.06) + break + } + } + } + } + + if gesture.state == .began { + cpk_handleTouchBegin() + + } else if gesture.state == .changed { + if let linkInfo = self.cpkSelectedLinkInfo { + let point = gesture.location(in: self) + if linkInfo.shouldCancelTouchAt(point: point) { + cpk_removeHighlightedLayers() + } + } + + } else if gesture.state == .ended || gesture.state == .cancelled { + if let linkInfo = self.cpkSelectedLinkInfo { + let callback = self.cpkLinkHandler + cpk_removeHighlightedLayers() + + if let closure = callback as? (String)->() { + closure(linkInfo.text) + } else if let closure = callback as? (String, NSRange)->() { + closure(linkInfo.text, linkInfo.range) + } else if let closure = callback as? (Any)->() { + closure(linkInfo.text) + } + } + } + } + + func cpk_addHighlightedLayers(for linkInfo: LinkInfo) { + for rect in linkInfo.boundingRects { + if rect.size.width > 0 && rect.size.height > 0 { + var color = self.cpkLinkSelectionColor ?? UIColor.darkGray + let radius = self.cpkLinkSelectionRadius ?? 4 + + if color.cgColor.alpha == 1 { + color = color.withAlphaComponent(0.4) + } + + let layer = CALayer() + layer.frame = rect + layer.cornerRadius = radius + layer.backgroundColor = color.cgColor + + self.layer.addSublayer(layer) + self.cpkSelectionLayers.append(layer) + } + } + } + + func cpk_removeHighlightedLayers() { + NSObject.cancelPreviousPerformRequests(withTarget: self, + selector: #selector(cpk_addHighlightedLayers(for:)), + object: self.cpkSelectedLinkInfo) + + for layer in self.cpkSelectionLayers { + layer.removeFromSuperlayer() + } + + self.cpkSelectionLayers.removeAll() + self.cpkSelectedLinkInfo = nil + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +//MARK: Override + +extension UILabel { + + @discardableResult + override public func bg(_ any: Any) -> Self { + super.bg(any) + return self + } + + @discardableResult + override public func tint(_ any: Any) -> Self { + super.tint(any) + return self + } + + @discardableResult + override public func radius(_ cornerRadius: CGFloat) -> Self { + super.radius(cornerRadius) + return self + } + + @discardableResult + override public func border(_ borderWidth: CGFloat, _ borderColor: Any? = nil) -> Self { + super.border(borderWidth, borderColor) + return self + } + + @discardableResult + override public func shadow(_ shadowOpacity: CGFloat, + _ shadowRadius: CGFloat = 3, + _ shadowOffsetX: CGFloat = 0, + _ shadowOffsetY: CGFloat = 3) -> Self { + + super.shadow(shadowOpacity, shadowRadius, shadowOffsetX, shadowOffsetY) + return self + } + + @discardableResult + override func onClick(_ closure: @escaping (UILabel)->()) -> Self { + cpk_onClick(closure, nil) + return self + } + + @discardableResult + override public func addTo(_ superView: UIView) -> Self { + super.addTo(superView) + return self + } + + @discardableResult + override public func styles(_ s1: Any, _ s2: Any? = nil, _ s3: Any? = nil, _ s4: Any? = nil) -> Self { + super.styles(s1, s2, s3, s4) + return self + } + + @discardableResult + override public func touchInsets(_ p1: Any, _ p2: Any? = nil, _ p3: Any? = nil, _ p4: Any? = nil) -> Self { + super.touchInsets(p1, p2, p3, p4) + return self + } + + @discardableResult + override public func margin(_ p1: Any, _ p2: Any? = nil, _ p3: Any? = nil, _ p4: Any? = nil) -> Self { + super.margin(p1, p2, p3, p4) + return self + } + + @discardableResult + override public func embedIn(_ superview: UIView, + _ p1: Any? = "", _ p2: Any? = "", + _ p3: Any? = "", _ p4: Any? = "") -> Self { + super.embedIn(superview, p1, p2, p3, p4) + return self + } + + @discardableResult + override public func makeCons(_ closure: (ConsMaker)->()) -> Self { + super.makeCons(closure) + return self + } + + @discardableResult + override public func remakeCons(_ closure: (ConsMaker)->()) -> Self { + super.remakeCons(closure) + return self + } +} + + + +extension UIImageView { + + @discardableResult + override public func bg(_ any: Any) -> Self { + super.bg(any) + return self + } + + @discardableResult + override public func tint(_ any: Any) -> Self { + super.tint(any) + return self + } + + @discardableResult + override public func radius(_ cornerRadius: CGFloat) -> Self { + super.radius(cornerRadius) + return self + } + + @discardableResult + override public func border(_ borderWidth: CGFloat, _ borderColor: Any? = nil) -> Self { + super.border(borderWidth, borderColor) + return self + } + + @discardableResult + override public func shadow(_ shadowOpacity: CGFloat, + _ shadowRadius: CGFloat = 3, + _ shadowOffsetX: CGFloat = 0, + _ shadowOffsetY: CGFloat = 3) -> Self { + + super.shadow(shadowOpacity, shadowRadius, shadowOffsetX, shadowOffsetY) + return self + } + + @discardableResult + override func onClick(_ closure: @escaping (UIImageView)->()) -> Self { + cpk_onClick(closure, nil) + return self + } + + @discardableResult + override public func addTo(_ superView: UIView) -> Self { + super.addTo(superView) + return self + } + + @discardableResult + override public func styles(_ s1: Any, _ s2: Any? = nil, _ s3: Any? = nil, _ s4: Any? = nil) -> Self { + super.styles(s1, s2, s3, s4) + return self + } + + @discardableResult + override public func touchInsets(_ p1: Any, _ p2: Any? = nil, _ p3: Any? = nil, _ p4: Any? = nil) -> Self { + super.touchInsets(p1, p2, p3, p4) + return self + } + + @discardableResult + override public func margin(_ p1: Any, _ p2: Any? = nil, _ p3: Any? = nil, _ p4: Any? = nil) -> Self { + super.margin(p1, p2, p3, p4) + return self + } + + @discardableResult + override public func embedIn(_ superview: UIView, + _ p1: Any? = "", _ p2: Any? = "", + _ p3: Any? = "", _ p4: Any? = "") -> Self { + super.embedIn(superview, p1, p2, p3, p4) + return self + } + + @discardableResult + override public func makeCons(_ closure: (ConsMaker)->()) -> Self { + super.makeCons(closure) + return self + } + + @discardableResult + override public func remakeCons(_ closure: (ConsMaker)->()) -> Self { + super.remakeCons(closure) + return self + } +} + + + +extension UIButton { + + @discardableResult + override public func radius(_ cornerRadius: CGFloat) -> Self { + super.radius(cornerRadius) + return self + } + + @discardableResult + override public func tint(_ any: Any) -> Self { + super.tint(any) + return self + } + + @discardableResult + override public func border(_ borderWidth: CGFloat, _ borderColor: Any? = nil) -> Self { + super.border(borderWidth, borderColor) + return self + } + + @discardableResult + override public func shadow(_ shadowOpacity: CGFloat, + _ shadowRadius: CGFloat = 3, + _ shadowOffsetX: CGFloat = 0, + _ shadowOffsetY: CGFloat = 3) -> Self { + + super.shadow(shadowOpacity, shadowRadius, shadowOffsetX, shadowOffsetY) + return self + } + + @discardableResult + override func onClick(_ closure: @escaping (UIButton)->()) -> Self { + cpk_onClick(closure, nil) + return self + } + + @discardableResult + override public func addTo(_ superView: UIView) -> Self { + super.addTo(superView) + return self + } + + @discardableResult + override public func styles(_ s1: Any, _ s2: Any? = nil, _ s3: Any? = nil, _ s4: Any? = nil) -> Self { + super.styles(s1, s2, s3, s4) + return self + } + + @discardableResult + override public func touchInsets(_ p1: Any, _ p2: Any? = nil, _ p3: Any? = nil, _ p4: Any? = nil) -> Self { + super.touchInsets(p1, p2, p3, p4) + return self + } + + @discardableResult + override public func margin(_ p1: Any, _ p2: Any? = nil, _ p3: Any? = nil, _ p4: Any? = nil) -> Self { + super.margin(p1, p2, p3, p4) + return self + } + + @discardableResult + override public func embedIn(_ superview: UIView, + _ p1: Any? = "", _ p2: Any? = "", + _ p3: Any? = "", _ p4: Any? = "") -> Self { + super.embedIn(superview, p1, p2, p3, p4) + return self + } + + @discardableResult + override public func makeCons(_ closure: (ConsMaker)->()) -> Self { + super.makeCons(closure) + return self + } + + @discardableResult + override public func remakeCons(_ closure: (ConsMaker)->()) -> Self { + super.remakeCons(closure) + return self + } +} + + + +extension UITextField { + + @discardableResult + override public func bg(_ any: Any) -> Self { + super.bg(any) + return self + } + + @discardableResult + override public func tint(_ any: Any) -> Self { + super.tint(any) + return self + } + + @discardableResult + override public func radius(_ cornerRadius: CGFloat) -> Self { + super.radius(cornerRadius) + return self + } + + @discardableResult + override public func border(_ borderWidth: CGFloat, _ borderColor: Any? = nil) -> Self { + super.border(borderWidth, borderColor) + return self + } + + @discardableResult + override public func shadow(_ shadowOpacity: CGFloat, + _ shadowRadius: CGFloat = 3, + _ shadowOffsetX: CGFloat = 0, + _ shadowOffsetY: CGFloat = 3) -> Self { + + super.shadow(shadowOpacity, shadowRadius, shadowOffsetX, shadowOffsetY) + return self + } + + @discardableResult + override func onClick(_ closure: @escaping (UITextField)->()) -> Self { + cpk_onClick(closure, nil) + return self + } + + @discardableResult + override public func addTo(_ superView: UIView) -> Self { + super.addTo(superView) + return self + } + + @discardableResult + override public func styles(_ s1: Any, _ s2: Any? = nil, _ s3: Any? = nil, _ s4: Any? = nil) -> Self { + super.styles(s1, s2, s3, s4) + return self + } + + @discardableResult + override public func touchInsets(_ p1: Any, _ p2: Any? = nil, _ p3: Any? = nil, _ p4: Any? = nil) -> Self { + super.touchInsets(p1, p2, p3, p4) + return self + } + + @discardableResult + override public func margin(_ p1: Any, _ p2: Any? = nil, _ p3: Any? = nil, _ p4: Any? = nil) -> Self { + super.margin(p1, p2, p3, p4) + return self + } + + @discardableResult + override public func embedIn(_ superview: UIView, + _ p1: Any? = "", _ p2: Any? = "", + _ p3: Any? = "", _ p4: Any? = "") -> Self { + super.embedIn(superview, p1, p2, p3, p4) + return self + } + + @discardableResult + override public func makeCons(_ closure: (ConsMaker)->()) -> Self { + super.makeCons(closure) + return self + } + + @discardableResult + override public func remakeCons(_ closure: (ConsMaker)->()) -> Self { + super.remakeCons(closure) + return self + } +} + + + +extension UITextView { + + @discardableResult + override public func bg(_ any: Any) -> Self { + super.bg(any) + return self + } + + @discardableResult + override public func tint(_ any: Any) -> Self { + super.tint(any) + return self + } + + @discardableResult + override public func radius(_ cornerRadius: CGFloat) -> Self { + super.radius(cornerRadius) + return self + } + + @discardableResult + override public func border(_ borderWidth: CGFloat, _ borderColor: Any? = nil) -> Self { + super.border(borderWidth, borderColor) + return self + } + + @discardableResult + override public func shadow(_ shadowOpacity: CGFloat, + _ shadowRadius: CGFloat = 3, + _ shadowOffsetX: CGFloat = 0, + _ shadowOffsetY: CGFloat = 3) -> Self { + + super.shadow(shadowOpacity, shadowRadius, shadowOffsetX, shadowOffsetY) + return self + } + + @discardableResult + override func onClick(_ closure: @escaping (UITextView)->()) -> Self { + cpk_onClick(closure, nil) + return self + } + + @discardableResult + override public func addTo(_ superView: UIView) -> Self { + super.addTo(superView) + return self + } + + @discardableResult + override public func styles(_ s1: Any, _ s2: Any? = nil, _ s3: Any? = nil, _ s4: Any? = nil) -> Self { + super.styles(s1, s2, s3, s4) + return self + } + + @discardableResult + override public func touchInsets(_ p1: Any, _ p2: Any? = nil, _ p3: Any? = nil, _ p4: Any? = nil) -> Self { + super.touchInsets(p1, p2, p3, p4) + return self + } + + @discardableResult + override public func margin(_ p1: Any, _ p2: Any? = nil, _ p3: Any? = nil, _ p4: Any? = nil) -> Self { + super.margin(p1, p2, p3, p4) + return self + } + + @discardableResult + override public func embedIn(_ superview: UIView, + _ p1: Any? = "", _ p2: Any? = "", + _ p3: Any? = "", _ p4: Any? = "") -> Self { + super.embedIn(superview, p1, p2, p3, p4) + return self + } + + @discardableResult + override public func makeCons(_ closure: (ConsMaker)->()) -> Self { + super.makeCons(closure) + return self + } + + @discardableResult + override public func remakeCons(_ closure: (ConsMaker)->()) -> Self { + super.remakeCons(closure) + return self + } +} + + + +extension CPKStackView { + + @discardableResult + override public func bg(_ any: Any) -> Self { + super.bg(any) + return self + } + + @discardableResult + override public func tint(_ any: Any) -> Self { + super.tint(any) + return self + } + + @discardableResult + override public func radius(_ cornerRadius: CGFloat) -> Self { + super.radius(cornerRadius) + return self + } + + @discardableResult + override public func border(_ borderWidth: CGFloat, _ borderColor: Any? = nil) -> Self { + super.border(borderWidth, borderColor) + return self + } + + @discardableResult + override public func shadow(_ shadowOpacity: CGFloat, + _ shadowRadius: CGFloat = 3, + _ shadowOffsetX: CGFloat = 0, + _ shadowOffsetY: CGFloat = 3) -> Self { + + super.shadow(shadowOpacity, shadowRadius, shadowOffsetX, shadowOffsetY) + return self + } + + @discardableResult + override func onClick(_ closure: @escaping (CPKStackView)->()) -> Self { + cpk_onClick(closure, nil) + return self + } + + @discardableResult + override public func addTo(_ superView: UIView) -> Self { + super.addTo(superView) + return self + } + + @discardableResult + override public func styles(_ s1: Any, _ s2: Any? = nil, _ s3: Any? = nil, _ s4: Any? = nil) -> Self { + super.styles(s1, s2, s3, s4) + return self + } + + @discardableResult + override public func touchInsets(_ p1: Any, _ p2: Any? = nil, _ p3: Any? = nil, _ p4: Any? = nil) -> Self { + super.touchInsets(p1, p2, p3, p4) + return self + } + + @discardableResult + override public func margin(_ p1: Any, _ p2: Any? = nil, _ p3: Any? = nil, _ p4: Any? = nil) -> Self { + super.margin(p1, p2, p3, p4) + return self + } + + @discardableResult + override public func embedIn(_ superview: UIView, + _ p1: Any? = "", _ p2: Any? = "", + _ p3: Any? = "", _ p4: Any? = "") -> Self { + super.embedIn(superview, p1, p2, p3, p4) + return self + } + + @discardableResult + override public func makeCons(_ closure: (ConsMaker)->()) -> Self { + super.makeCons(closure) + return self + } + + @discardableResult + override public func remakeCons(_ closure: (ConsMaker)->()) -> Self { + super.remakeCons(closure) + return self + } +} + + +extension StaticTableView { + @discardableResult + override public func bg(_ any: Any) -> Self { + super.bg(any) + return self + } + + @discardableResult + override public func tint(_ any: Any) -> Self { + super.tint(any) + return self + } + + @discardableResult + override public func radius(_ cornerRadius: CGFloat) -> Self { + super.radius(cornerRadius) + return self + } + + @discardableResult + override public func border(_ borderWidth: CGFloat, _ borderColor: Any? = nil) -> Self { + super.border(borderWidth, borderColor) + return self + } + + @discardableResult + override public func shadow(_ shadowOpacity: CGFloat, + _ shadowRadius: CGFloat = 3, + _ shadowOffsetX: CGFloat = 0, + _ shadowOffsetY: CGFloat = 3) -> Self { + + super.shadow(shadowOpacity, shadowRadius, shadowOffsetX, shadowOffsetY) + return self + } + + @discardableResult + override public func addTo(_ superView: UIView) -> Self { + super.addTo(superView) + return self + } + + @discardableResult + override public func margin(_ p1: Any, _ p2: Any? = nil, _ p3: Any? = nil, _ p4: Any? = nil) -> Self { + super.margin(p1, p2, p3, p4) + return self + } + + @discardableResult + override public func embedIn(_ superview: UIView, + _ p1: Any? = "", _ p2: Any? = "", + _ p3: Any? = "", _ p4: Any? = "") -> Self { + super.embedIn(superview, p1, p2, p3, p4) + return self + } + + @discardableResult + override public func makeCons(_ closure: (ConsMaker)->()) -> Self { + super.makeCons(closure) + return self + } + + @discardableResult + override public func remakeCons(_ closure: (ConsMaker)->()) -> Self { + super.remakeCons(closure) + return self + } +} + + + + + + + + + + + + + diff --git a/res/appstore.png b/res/appstore.png new file mode 100644 index 0000000..32df3f9 Binary files /dev/null and b/res/appstore.png differ diff --git a/res/cupcake.png b/res/cupcake.png new file mode 100644 index 0000000..3296122 Binary files /dev/null and b/res/cupcake.png differ