From bb74b3820d80f9d65c18794f4c958e10d7d92a4b Mon Sep 17 00:00:00 2001 From: Xavier Schott Date: Sun, 25 Jan 2015 17:54:02 -0800 Subject: [PATCH] TGPControls, custom awesome iOS controls --- TGPControls.podspec | 126 +++++ TGPControls.xcodeproj/project.pbxproj | 395 ++++++++++++++ .../contents.xcworkspacedata | 7 + TGPControls/TGPCamelLabels.h | 48 ++ TGPControls/TGPCamelLabels.m | 35 ++ TGPControls/TGPCamelLabels7.h | 49 ++ TGPControls/TGPCamelLabels7.m | 285 ++++++++++ TGPControls/TGPDiscreteSlider.h | 55 ++ TGPControls/TGPDiscreteSlider.m | 47 ++ TGPControls/TGPDiscreteSlider7.h | 70 +++ TGPControls/TGPDiscreteSlider7.m | 486 ++++++++++++++++++ TGPControlsTests/Info.plist | 24 + TGPControls_License.txt | 19 + 13 files changed, 1646 insertions(+) create mode 100644 TGPControls.podspec create mode 100644 TGPControls.xcodeproj/project.pbxproj create mode 100644 TGPControls.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 TGPControls/TGPCamelLabels.h create mode 100644 TGPControls/TGPCamelLabels.m create mode 100644 TGPControls/TGPCamelLabels7.h create mode 100644 TGPControls/TGPCamelLabels7.m create mode 100644 TGPControls/TGPDiscreteSlider.h create mode 100644 TGPControls/TGPDiscreteSlider.m create mode 100644 TGPControls/TGPDiscreteSlider7.h create mode 100644 TGPControls/TGPDiscreteSlider7.m create mode 100644 TGPControlsTests/Info.plist create mode 100644 TGPControls_License.txt diff --git a/TGPControls.podspec b/TGPControls.podspec new file mode 100644 index 0000000..3f924c1 --- /dev/null +++ b/TGPControls.podspec @@ -0,0 +1,126 @@ +# +# Be sure to run `pod spec lint TGPControls.podspec' to ensure this is a +# valid spec and to remove all comments including this before submitting the spec. +# +# To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html +# To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/ +# + +Pod::Spec.new do |s| + + # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # + # + # These will help people to find your library, and whilst it + # can feel like a chore to fill in it's definitely to your advantage. The + # summary should be tweet-length, and the description more in depth. + # + + s.name = "TGPControls" + s.version = "0.0.1" + s.summary = "Custom Awesome iOS Controls." + + s.description = <<-DESC + Provide an iOS looking UISlider with discrete, controlable steps + Provide dynamic, animated labels for discrete slider + + DESC + + s.homepage = "https://github.com/arquebuse/TGPControls" + # s.screenshots = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif" + + + # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # + # + # Licensing your code is important. See http://choosealicense.com for more info. + # CocoaPods will detect a license file if there is a named LICENSE* + # Popular ones are 'MIT', 'BSD' and 'Apache License, Version 2.0'. + # + + # s.license = "MIT (example)" + s.license = { :type => "MIT", :file => "TGPControls_License.txt" } + + + # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # + # + # Specify the authors of the library, with email addresses. Email addresses + # of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also + # accepts just a name if you'd rather not provide an email address. + # + # Specify a social_media_url where others can refer to, for example a twitter + # profile URL. + # + + s.author = { "Arquebuse" => "xschott@gmail.com" } + s.social_media_url = "http://twitter.com/xavierschott" + + # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― # + # + # If this Pod runs only on iOS or OS X, then specify the platform and + # the deployment target. You can optionally include the target after the platform. + # + + s.platform = :ios, "7.0" + + + # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # + # + # Specify the location from where the source should be retrieved. + # Supports git, hg, bzr, svn and HTTP. + # + + s.source = { :git => "https://github.com/arquebuse/TGPControls.git", :tag => "0.0.1" } + + + # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # + # + # CocoaPods is smart about how it includes source code. For source files + # giving a folder will include any h, m, mm, c & cpp files. For header + # files it will include any header in the folder. + # Not including the public_header_files will make all headers public. + # + + s.source_files = "TGPControls", "TGPControls/**/*.{h,m}" + # s.exclude_files = "TGPControls/**/*.{h,m}" + + s.public_header_files = "TGPControls/**/*.{h}" + + + # ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # + # + # A list of resources included with the Pod. These are copied into the + # target bundle with a build phase script. Anything else will be cleaned. + # You can preserve files from being cleaned, please don't preserve + # non-essential files like tests, examples and documentation. + # + + # s.resource = "TGPImages.car" + # s.resources = "TGPImages.xcassets/*.imageset" + + # s.preserve_paths = "FilesToSave", "MoreFilesToSave" + + + # ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # + # + # Link your library with frameworks, or libraries. Libraries do not include + # the lib prefix of their name. + # + + # s.framework = "SomeFramework" + # s.frameworks = "SomeFramework", "AnotherFramework" + + # s.library = "iconv" + # s.libraries = "iconv", "xml2" + + + # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # + # + # If your library depends on compiler flags you can set them in the xcconfig hash + # where they will only apply to your library. If you depend on other Podspecs + # you can include multiple dependencies to ensure it works. + + s.requires_arc = true + + # s.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" } + # s.dependency "JSONKit", "~> 1.4" + +end diff --git a/TGPControls.xcodeproj/project.pbxproj b/TGPControls.xcodeproj/project.pbxproj new file mode 100644 index 0000000..930e7ef --- /dev/null +++ b/TGPControls.xcodeproj/project.pbxproj @@ -0,0 +1,395 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + DC0468E31A75962E0084DE3E /* TGPCamelLabels7.m in Sources */ = {isa = PBXBuildFile; fileRef = DC0468E01A75962E0084DE3E /* TGPCamelLabels7.m */; }; + DC0468E41A75962E0084DE3E /* TGPDiscreteSlider7.m in Sources */ = {isa = PBXBuildFile; fileRef = DC0468E21A75962E0084DE3E /* TGPDiscreteSlider7.m */; }; + DC0468E71A7596690084DE3E /* TGPCamelLabels.m in Sources */ = {isa = PBXBuildFile; fileRef = DC0468E61A7596690084DE3E /* TGPCamelLabels.m */; }; + DC0468EA1A7596930084DE3E /* TGPDiscreteSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = DC0468E91A7596930084DE3E /* TGPDiscreteSlider.m */; }; + DC101D661A75749600ECCF80 /* libTGPControls.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DC101D5A1A75749600ECCF80 /* libTGPControls.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + DC101D671A75749600ECCF80 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DC101D521A75749600ECCF80 /* Project object */; + proxyType = 1; + remoteGlobalIDString = DC101D591A75749600ECCF80; + remoteInfo = TGPControls; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + DC101D581A75749600ECCF80 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + DC0468DF1A75962E0084DE3E /* TGPCamelLabels7.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGPCamelLabels7.h; sourceTree = ""; }; + DC0468E01A75962E0084DE3E /* TGPCamelLabels7.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGPCamelLabels7.m; sourceTree = ""; }; + DC0468E11A75962E0084DE3E /* TGPDiscreteSlider7.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGPDiscreteSlider7.h; sourceTree = ""; }; + DC0468E21A75962E0084DE3E /* TGPDiscreteSlider7.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGPDiscreteSlider7.m; sourceTree = ""; }; + DC0468E51A7596690084DE3E /* TGPCamelLabels.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGPCamelLabels.h; sourceTree = ""; }; + DC0468E61A7596690084DE3E /* TGPCamelLabels.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGPCamelLabels.m; sourceTree = ""; }; + DC0468E81A7596930084DE3E /* TGPDiscreteSlider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGPDiscreteSlider.h; sourceTree = ""; }; + DC0468E91A7596930084DE3E /* TGPDiscreteSlider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGPDiscreteSlider.m; sourceTree = ""; }; + DC101D5A1A75749600ECCF80 /* libTGPControls.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libTGPControls.a; sourceTree = BUILT_PRODUCTS_DIR; }; + DC101D651A75749600ECCF80 /* TGPControlsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TGPControlsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + DC101D6B1A75749600ECCF80 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + DC101D7A1A75756300ECCF80 /* TGPControls_License.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = TGPControls_License.txt; sourceTree = ""; }; + DC101D7B1A75756300ECCF80 /* TGPControls.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = TGPControls.podspec; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + DC101D571A75749600ECCF80 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DC101D621A75749600ECCF80 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DC101D661A75749600ECCF80 /* libTGPControls.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + DC0468DE1A75962E0084DE3E /* TGPControls */ = { + isa = PBXGroup; + children = ( + DC0468E51A7596690084DE3E /* TGPCamelLabels.h */, + DC0468E61A7596690084DE3E /* TGPCamelLabels.m */, + DC0468E81A7596930084DE3E /* TGPDiscreteSlider.h */, + DC0468E91A7596930084DE3E /* TGPDiscreteSlider.m */, + DC0468DF1A75962E0084DE3E /* TGPCamelLabels7.h */, + DC0468E01A75962E0084DE3E /* TGPCamelLabels7.m */, + DC0468E11A75962E0084DE3E /* TGPDiscreteSlider7.h */, + DC0468E21A75962E0084DE3E /* TGPDiscreteSlider7.m */, + ); + path = TGPControls; + sourceTree = ""; + }; + DC101D511A75749600ECCF80 = { + isa = PBXGroup; + children = ( + DC101D7A1A75756300ECCF80 /* TGPControls_License.txt */, + DC101D7B1A75756300ECCF80 /* TGPControls.podspec */, + DC0468DE1A75962E0084DE3E /* TGPControls */, + DC101D691A75749600ECCF80 /* TGPControlsTests */, + DC101D5B1A75749600ECCF80 /* Products */, + ); + sourceTree = ""; + }; + DC101D5B1A75749600ECCF80 /* Products */ = { + isa = PBXGroup; + children = ( + DC101D5A1A75749600ECCF80 /* libTGPControls.a */, + DC101D651A75749600ECCF80 /* TGPControlsTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + DC101D691A75749600ECCF80 /* TGPControlsTests */ = { + isa = PBXGroup; + children = ( + DC101D6A1A75749600ECCF80 /* Supporting Files */, + ); + path = TGPControlsTests; + sourceTree = ""; + }; + DC101D6A1A75749600ECCF80 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + DC101D6B1A75749600ECCF80 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + DC101D591A75749600ECCF80 /* TGPControls */ = { + isa = PBXNativeTarget; + buildConfigurationList = DC101D6E1A75749600ECCF80 /* Build configuration list for PBXNativeTarget "TGPControls" */; + buildPhases = ( + DC101D561A75749600ECCF80 /* Sources */, + DC101D571A75749600ECCF80 /* Frameworks */, + DC101D581A75749600ECCF80 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = TGPControls; + productName = TGPControls; + productReference = DC101D5A1A75749600ECCF80 /* libTGPControls.a */; + productType = "com.apple.product-type.library.static"; + }; + DC101D641A75749600ECCF80 /* TGPControlsTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = DC101D711A75749600ECCF80 /* Build configuration list for PBXNativeTarget "TGPControlsTests" */; + buildPhases = ( + DC101D611A75749600ECCF80 /* Sources */, + DC101D621A75749600ECCF80 /* Frameworks */, + DC101D631A75749600ECCF80 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + DC101D681A75749600ECCF80 /* PBXTargetDependency */, + ); + name = TGPControlsTests; + productName = TGPControlsTests; + productReference = DC101D651A75749600ECCF80 /* TGPControlsTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + DC101D521A75749600ECCF80 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0610; + ORGANIZATIONNAME = arquebuse; + TargetAttributes = { + DC101D591A75749600ECCF80 = { + CreatedOnToolsVersion = 6.1.1; + }; + DC101D641A75749600ECCF80 = { + CreatedOnToolsVersion = 6.1.1; + }; + }; + }; + buildConfigurationList = DC101D551A75749600ECCF80 /* Build configuration list for PBXProject "TGPControls" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = DC101D511A75749600ECCF80; + productRefGroup = DC101D5B1A75749600ECCF80 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + DC101D591A75749600ECCF80 /* TGPControls */, + DC101D641A75749600ECCF80 /* TGPControlsTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + DC101D631A75749600ECCF80 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + DC101D561A75749600ECCF80 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DC0468E31A75962E0084DE3E /* TGPCamelLabels7.m in Sources */, + DC0468E71A7596690084DE3E /* TGPCamelLabels.m in Sources */, + DC0468E41A75962E0084DE3E /* TGPDiscreteSlider7.m in Sources */, + DC0468EA1A7596930084DE3E /* TGPDiscreteSlider.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DC101D611A75749600ECCF80 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + DC101D681A75749600ECCF80 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DC101D591A75749600ECCF80 /* TGPControls */; + targetProxy = DC101D671A75749600ECCF80 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + DC101D6C1A75749600ECCF80 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + DC101D6D1A75749600ECCF80 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + DC101D6F1A75749600ECCF80 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + DC101D701A75749600ECCF80 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Release; + }; + DC101D721A75749600ECCF80 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = TGPControlsTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + DC101D731A75749600ECCF80 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = TGPControlsTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + DC101D551A75749600ECCF80 /* Build configuration list for PBXProject "TGPControls" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DC101D6C1A75749600ECCF80 /* Debug */, + DC101D6D1A75749600ECCF80 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + DC101D6E1A75749600ECCF80 /* Build configuration list for PBXNativeTarget "TGPControls" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DC101D6F1A75749600ECCF80 /* Debug */, + DC101D701A75749600ECCF80 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + DC101D711A75749600ECCF80 /* Build configuration list for PBXNativeTarget "TGPControlsTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DC101D721A75749600ECCF80 /* Debug */, + DC101D731A75749600ECCF80 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = DC101D521A75749600ECCF80 /* Project object */; +} diff --git a/TGPControls.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/TGPControls.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..535f81b --- /dev/null +++ b/TGPControls.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/TGPControls/TGPCamelLabels.h b/TGPControls/TGPCamelLabels.h new file mode 100644 index 0000000..2225f85 --- /dev/null +++ b/TGPControls/TGPCamelLabels.h @@ -0,0 +1,48 @@ +// @file: TGPCamelLabels.h +// @project: TGPControls +// +// @history: Created November 27, 2014 (Thanksgiving Day) +// @author: Xavier Schott +// mailto://xschott@gmail.com +// http://thegothicparty.com +// tel://+18089383634 +// +// @license: http://opensource.org/licenses/MIT +// Copyright (c) 2014, Xavier Schott +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "TGPCamelLabels7.h" + +IB_DESIGNABLE +@interface TGPCamelLabels : TGPCamelLabels7 + +@property (nonatomic) IBInspectable NSUInteger tickCount; // Only used if [labels count] < 1 +@property (nonatomic) IBInspectable CGFloat ticksDistance; +@property (nonatomic) IBInspectable NSUInteger value; + +@property (nonatomic) IBInspectable NSString * upFontName; +@property (nonatomic) IBInspectable CGFloat upFontSize; +@property (nonatomic) IBInspectable UIColor * upFontColor; + +@property (nonatomic) IBInspectable NSString * downFontName; +@property (nonatomic) IBInspectable CGFloat downFontSize; +@property (nonatomic) IBInspectable UIColor * downFontColor; + +@end diff --git a/TGPControls/TGPCamelLabels.m b/TGPControls/TGPCamelLabels.m new file mode 100644 index 0000000..ae4b87b --- /dev/null +++ b/TGPControls/TGPCamelLabels.m @@ -0,0 +1,35 @@ +// @file: TGPCamelLabels.m +// @project: TGPControls +// +// @history: Created November 27, 2014 (Thanksgiving Day) +// @author: Xavier Schott +// mailto://xschott@gmail.com +// http://thegothicparty.com +// tel://+18089383634 +// +// @license: http://opensource.org/licenses/MIT +// Copyright (c) 2014, Xavier Schott +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "TGPCamelLabels.h" + +@implementation TGPCamelLabels + +@end diff --git a/TGPControls/TGPCamelLabels7.h b/TGPControls/TGPCamelLabels7.h new file mode 100644 index 0000000..9cb66e5 --- /dev/null +++ b/TGPControls/TGPCamelLabels7.h @@ -0,0 +1,49 @@ +// @file: TGPCamelLabels7.h +// @project: TGPControls +// +// @history: Created July 4th, 2014 (Independence Day) +// @author: Xavier Schott +// mailto://xschott@gmail.com +// http://thegothicparty.com +// tel://+18089383634 +// +// @license: http://opensource.org/licenses/MIT +// Copyright (c) 2014, Xavier Schott +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +@interface TGPCamelLabels7 : UIControl + +@property (nonatomic) NSUInteger tickCount; // Only used if [labels count] < 1 +@property (nonatomic) CGFloat ticksDistance; +@property (nonatomic) NSUInteger value; + +@property (nonatomic) NSString * upFontName; +@property (nonatomic) CGFloat upFontSize; +@property (nonatomic) UIColor * upFontColor; + +@property (nonatomic) NSString * downFontName; +@property (nonatomic) CGFloat downFontSize; +@property (nonatomic) UIColor * downFontColor; + +@property (nonatomic) NSArray * names; // Will dictate the number of ticks + +@end diff --git a/TGPControls/TGPCamelLabels7.m b/TGPControls/TGPCamelLabels7.m new file mode 100644 index 0000000..6ef181d --- /dev/null +++ b/TGPControls/TGPCamelLabels7.m @@ -0,0 +1,285 @@ +// @file: TGPCamelLabels7.m +// @project: TGPControls +// +// @history: Created July 4th, 2014 (Independence Day) +// @author: Xavier Schott +// mailto://xschott@gmail.com +// http://thegothicparty.com +// tel://+18089383634 +// +// @license: http://opensource.org/licenses/MIT +// Copyright (c) 2014, Xavier Schott +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "TGPCamelLabels7.h" + +@interface TGPCamelLabels7() +@property (nonatomic, assign) NSUInteger lastValue; +@property (nonatomic, retain) NSMutableArray * upLabels; +@property (nonatomic, retain) NSMutableArray * dnLabels; +@end + +@implementation TGPCamelLabels7 + +#pragma mark properties + +- (void)setTickCount:(NSUInteger)tickCount { + // calculated property + // Put some order to tickCount: 1 >= count >= 128 + const unsigned int count = (unsigned int) MAX(1, MIN(tickCount, 128)); + [self debugNames:count]; + [self layoutTrack]; +} + +- (NSUInteger)tickCount { + // Dynamic property + return [_names count]; +} + +- (void)setTicksDistance:(CGFloat)ticksDistance { + _ticksDistance = MAX(0, ticksDistance); + [self layoutTrack]; +} + +- (void)setValue:(NSUInteger)value { + _value = value; + [self dockEffect:.15]; +} + +- (void)setUpFontName:(NSString *)upFontName { + _upFontName = upFontName; + [self layoutTrack]; +} + +- (void)setUpFontSize:(CGFloat)upFontSize { + _upFontSize = upFontSize; + [self layoutTrack]; +} + +- (void)setUpFontColor:(UIColor *)upFontColor { + _upFontColor = upFontColor; + [self layoutTrack]; +} + +- (void)setDownFontName:(NSString *)downFontName { + _downFontName = downFontName; + [self layoutTrack]; +} + +- (void)setDownFontSize:(CGFloat)downFontSize { + _downFontSize = downFontSize; + [self layoutTrack]; +} + +- (void)setDownFontColor:(UIColor *)downFontColor { + _downFontColor = downFontColor; + [self layoutTrack]; +} + +// NSArray +- (void)setNames:(NSArray *)names { + NSAssert(names.count > 0, @"names.count"); + _names = names; + [self layoutTrack]; +} + +#pragma mark UIView + +- (id)initWithCoder:(NSCoder *)aDecoder { + self = [super initWithCoder:aDecoder]; + if(self != nil) { + [self initProperties]; + } + return self; +} + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if(self != nil) { + [self initProperties]; + } + return self; +} + +// clickthrough +-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { + for (UIView *view in self.subviews) { + if (!view.hidden && view.alpha > 0 && view.userInteractionEnabled && [view pointInside:[self convertPoint:point toView:view] withEvent:event]) + return YES; + } + return NO; +} + +#pragma mark TGPCamelSlider + +- (void)initProperties { + _ticksDistance = 44.0; + _value = 0; + [self debugNames:10]; + + _upFontName = nil; + _upFontSize = 12; + _upFontColor = nil; + + _downFontName = nil; + _downFontSize = 12; + _downFontColor = nil; + + _upLabels = [NSMutableArray array]; + _dnLabels = [NSMutableArray array]; + + _lastValue = NSNotFound; // Never tapped + [self layoutTrack]; +} + +- (void)debugNames:(unsigned int)count { + // Dynamic property, will create an array with labels, generally for debugging purposes + const NSMutableArray * array = [NSMutableArray array]; + for(int iterate = 1; iterate <= count; iterate++) { + [array addObject:[NSString stringWithFormat:@"%d", iterate ]]; + } + [self setNames:(NSArray *) array]; +} + +- (void)layoutTrack { + [self.upLabels enumerateObjectsUsingBlock:^(UIView * view, NSUInteger idx, BOOL *stop) { + [view removeFromSuperview]; + }]; + [self.upLabels removeAllObjects]; + [self.dnLabels enumerateObjectsUsingBlock:^(UIView * view, NSUInteger idx, BOOL *stop) { + [view removeFromSuperview]; + }]; + [self.dnLabels removeAllObjects]; + + const NSUInteger count = self.names.count; + if( count > 0) { + CGFloat centerX = (self.bounds.size.width - ((count - 1) * self.ticksDistance))/2.0; + const CGFloat centerY = self.bounds.size.height / 2.0; + for(NSString * name in self.names) { + UILabel * upLabel = [[UILabel alloc] initWithFrame:CGRectZero]; + [self.upLabels addObject:upLabel]; + upLabel.text = name; + upLabel.font = ((self.upFontName != nil) + ? [UIFont fontWithName:self.upFontName size:self.upFontSize] + : [UIFont boldSystemFontOfSize:self.upFontSize]); + upLabel.textColor = ((self.upFontColor != nil) + ? self.upFontColor + : self.tintColor); + [upLabel sizeToFit]; + upLabel.center = CGPointMake(centerX, centerY); + upLabel.frame = ({ + CGRect frame = upLabel.frame; + // frame.origin.y = 0; + frame.origin.y = self.bounds.size.height - frame.size.height; + frame; + }); + upLabel.alpha = 0.0; + [self addSubview:upLabel]; + + UILabel * dnLabel = [[UILabel alloc] initWithFrame:CGRectZero]; + [self.dnLabels addObject:dnLabel]; + dnLabel.text = name; + dnLabel.font = ((self.downFontName != nil) + ? [UIFont fontWithName:self.downFontName size:self.downFontSize] + : [UIFont boldSystemFontOfSize:self.downFontSize]); + dnLabel.textColor = ((self.downFontColor != nil) + ? self.downFontColor + : [UIColor grayColor]); + [dnLabel sizeToFit]; + dnLabel.center = CGPointMake(centerX, centerY); + dnLabel.frame = ({ + CGRect frame = dnLabel.frame; + frame.origin.y = self.bounds.size.height - frame.size.height; + frame; + }); + [self addSubview:dnLabel]; + + centerX += self.ticksDistance; + } + [self dockEffect:0.0]; + } +} + +- (void)dockEffect:(NSTimeInterval)duration +{ + const NSUInteger up = self.value; + + // Unlike the National Parks from which it is inspired, this Dock Effect + // does not abruptly change from BOLD to plain. Instead, we have 2 sets of + // labels, which are faded back and forth, in unisson. + // - BOLD to plain + // - Black to gray + // - high to low + // Each animation picks up where the previous left off + void (^moveBlock)() = ^void() { + // Bring almost all down + [self.upLabels enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + if( up != idx) { + [self moveDown:obj withAlpha:0.f]; + } + }]; + [self.dnLabels enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + if( up != idx) { + [self moveDown:obj withAlpha:1.f]; + } + }]; + + // Bring the selection up + if(up < [self.upLabels count]) { + [self moveUp:[self.upLabels objectAtIndex:up] withAlpha:1.f]; + } + if(up < [self.dnLabels count]) { + [self moveUp:[self.dnLabels objectAtIndex:up] withAlpha:0.f]; + } + }; + + if(duration > 0) { + [UIView animateWithDuration:duration + delay:0 + options:(UIViewAnimationOptionBeginFromCurrentState + + UIViewAnimationOptionCurveLinear) + animations:moveBlock + completion:nil]; + } else { + moveBlock(); + } +} + +- (void)moveDown:(UIView*)aView withAlpha:(CGFloat) alpha +{ + aView.frame = ({ + CGRect frame = aView.frame; + frame.origin.y = self.bounds.size.height - frame.size.height; + frame; + }); + [aView setAlpha:alpha]; +} + +- (void)moveUp:(UIView*)aView withAlpha:(CGFloat) alpha +{ + aView.frame = ({ + CGRect frame = aView.frame; + frame.origin.y = 0; + frame; + }); + [aView setAlpha:alpha]; +} + +@end diff --git a/TGPControls/TGPDiscreteSlider.h b/TGPControls/TGPDiscreteSlider.h new file mode 100644 index 0000000..34cc36d --- /dev/null +++ b/TGPControls/TGPDiscreteSlider.h @@ -0,0 +1,55 @@ +// @file: TGPDiscreteSlider.h +// @project: TGPControls +// +// @history: Created November 27, 2014 (Thanksgiving Day) +// @author: Xavier Schott +// mailto://xschott@gmail.com +// http://thegothicparty.com +// tel://+18089383634 +// +// @license: http://opensource.org/licenses/MIT +// Copyright (c) 2014, Xavier Schott +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "TGPDiscreteSlider7.h" + +IB_DESIGNABLE +@interface TGPDiscreteSlider : TGPDiscreteSlider7 + +@property (nonatomic) IBInspectable int tickStyle; +@property (nonatomic) IBInspectable CGSize tickSize; +@property (nonatomic) IBInspectable int tickCount; + +@property (nonatomic) IBInspectable int trackStyle; +@property (nonatomic) IBInspectable CGFloat trackThickness; + +@property (nonatomic) IBInspectable int thumbStyle; +@property (nonatomic) IBInspectable CGSize thumbSize; +@property (nonatomic) IBInspectable UIColor * thumbColor; +@property (nonatomic) IBInspectable CGFloat thumbSRadius; +@property (nonatomic) IBInspectable CGSize thumbSOffset; + +// AKA: UISlider value (as CGFloat for compatibility with UISlider API, but expected to contain integers) +@property (nonatomic) IBInspectable CGFloat minimumValue; +@property (nonatomic) IBInspectable CGFloat value; + +@property (nonatomic) IBInspectable int incrementValue; + +@end diff --git a/TGPControls/TGPDiscreteSlider.m b/TGPControls/TGPDiscreteSlider.m new file mode 100644 index 0000000..aa6dcbf --- /dev/null +++ b/TGPControls/TGPDiscreteSlider.m @@ -0,0 +1,47 @@ +// @file: TGPDiscreteSlider.m +// @project: TGPControls +// +// @history: Created November 27, 2014 (Thanksgiving Day) +// @author: Xavier Schott +// mailto://xschott@gmail.com +// http://thegothicparty.com +// tel://+18089383634 +// +// @license: http://opensource.org/licenses/MIT +// Copyright (c) 2014, Xavier Schott +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "TGPDiscreteSlider.h" + +@implementation TGPDiscreteSlider + +#pragma mark properties + +- (void)setThumbSRadius:(CGFloat)thumbSRadius { + // adapter + self.thumbShadowRadius = thumbSRadius; +} + +- (void)setThumbSOffset:(CGSize)thumbSOffset { + // adapter + self.thumbShadowOffset = thumbSOffset; +} + +@end diff --git a/TGPControls/TGPDiscreteSlider7.h b/TGPControls/TGPDiscreteSlider7.h new file mode 100644 index 0000000..370b1d2 --- /dev/null +++ b/TGPControls/TGPDiscreteSlider7.h @@ -0,0 +1,70 @@ +// @file: TGPDiscreteSlider7.h +// @project: TGPControls +// +// @history: Created July 4th, 2014 (Independence Day) +// @author: Xavier Schott +// mailto://xschott@gmail.com +// http://thegothicparty.com +// tel://+18089383634 +// +// @license: http://opensource.org/licenses/MIT +// Copyright (c) 2014, Xavier Schott +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +@interface TGPDiscreteSlider7 : + +// Interface builder hides the IBInspectable for UIControl +#if TARGET_INTERFACE_BUILDER +UIView +#else // !TARGET_INTERFACE_BUILDER +UIControl +#endif // TARGET_INTERFACE_BUILDER + +typedef NS_ENUM(int, ComponentStyle) { + ComponentStyleIOS = 0, + ComponentStyleRectangular, + ComponentStyleRounded, + ComponentStyleInvisible, + ComponentStyleImage +}; + +@property (nonatomic) ComponentStyle tickStyle; +@property (nonatomic) CGSize tickSize; +@property (nonatomic) int tickCount; +@property (nonatomic, readonly) CGFloat ticksDistance; + +@property (nonatomic) ComponentStyle trackStyle; +@property (nonatomic) CGFloat trackThickness; + +@property (nonatomic) ComponentStyle thumbStyle; +@property (nonatomic) CGSize thumbSize; +@property (nonatomic) UIColor * thumbColor; +@property (nonatomic) CGFloat thumbShadowRadius; +@property (nonatomic) CGSize thumbShadowOffset; + +// AKA: UISlider value (as CGFloat for compatibility with UISlider API, but expected to contain integers) +@property (nonatomic) CGFloat minimumValue; +@property (nonatomic) CGFloat value; + +@property (nonatomic) int incrementValue; + +@end diff --git a/TGPControls/TGPDiscreteSlider7.m b/TGPControls/TGPDiscreteSlider7.m new file mode 100644 index 0000000..f5ce8b2 --- /dev/null +++ b/TGPControls/TGPDiscreteSlider7.m @@ -0,0 +1,486 @@ +// @file: TGPDiscreteSlider7.m +// @project: TGPControls +// +// @history: Created July 4th, 2014 (Independence Day) +// @author: Xavier Schott +// mailto://xschott@gmail.com +// http://thegothicparty.com +// tel://+18089383634 +// +// @license: http://opensource.org/licenses/MIT +// Copyright (c) 2014, Xavier Schott +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "TGPDiscreteSlider7.h" + +static CGFloat iOSThumbShadowRadius = 4.0; +static CGSize iosThumbShadowOffset = (CGSize){0, 3}; + +@interface TGPDiscreteSlider7 () { + int _intValue; + int _intMinimumValue; +} +@property (nonatomic) NSMutableArray * ticksAbscisses; +@property (nonatomic, assign) CGFloat thumbAbscisse; +@property (nonatomic) CALayer * thumbLayer; +@property (nonatomic) CALayer * colorTrackLayer; +@property (nonatomic) CGRect trackRectangle; +@end + +@implementation TGPDiscreteSlider7 + +#pragma mark properties + +- (void)setTickStyle:(ComponentStyle)tickStyle { + _tickStyle = tickStyle; + [self layoutTrack]; +} + +- (void)setTickSize:(CGSize)tickSize { + _tickSize.width = MAX(0, tickSize.width); + _tickSize.height = MAX(0, tickSize.height); + [self layoutTrack]; +} + +- (void)setTickCount:(int)tickCount { + _tickCount = MAX(2, tickCount); + [self layoutTrack]; +} + +- (CGFloat)ticksDistance { + NSAssert1(self.tickCount > 1, @"2 ticks minimum %d", self.tickCount); + const unsigned int segments = MAX(1, self.tickCount - 1); + const CGFloat thumbWidth = [self thumbSizeIncludingShadow].width; + const double trackLength = (double)(self.frame.size.width - thumbWidth); + + return (CGFloat) (trackLength / segments); +} + +- (void)setTrackStyle:(ComponentStyle)trackStyle { + _trackStyle = trackStyle; + [self layoutTrack]; +} + +- (void)setTrackThickness:(CGFloat)trackThickness { + _trackThickness = MAX(0, trackThickness); + [self layoutTrack]; +} + +- (void)setThumbStyle:(ComponentStyle)thumbStyle { + _thumbStyle = thumbStyle; + [self layoutTrack]; +} + +- (void)setThumbSize:(CGSize)thumbSize { + _thumbSize.width = MAX(1, thumbSize.width); + _thumbSize.height = MAX(1, thumbSize.height); + [self layoutTrack]; +} + +- (void)setThumbShadowRadius:(CGFloat)thumbShadowRadius { + _thumbShadowRadius = thumbShadowRadius; + [self layoutTrack]; +} + +- (void)setIncrementValue:(int)incrementValue { + _incrementValue = incrementValue; + if(0 == incrementValue) { + _incrementValue = 1; // nonZeroIncrement + } + [self layoutTrack]; +} + +// AKA: UISlider value (as CGFloat for compatibility with UISlider API, but expected to contain integers) +- (void)setMinimumValue:(CGFloat)minimumValue { + _intMinimumValue = minimumValue; + [self layoutTrack]; +} + +- (CGFloat)minimumValue { + return _intMinimumValue; // calculated property, with a float-to-int adapter +} + +- (void)setValue:(CGFloat)value { + const unsigned int nonZeroIncrement = ((0 == _incrementValue) ? 1 : _incrementValue); + const int rootValue = ((value - self.minimumValue) / nonZeroIncrement); + _intValue = self.minimumValue + (rootValue * nonZeroIncrement); + [self layoutTrack]; +} + +- (CGFloat)value { + return _intValue; // calculated property, with a float-to-int adapter +} + +#pragma mark UIControl + +- (id)initWithCoder:(NSCoder *)aDecoder { + self = [super initWithCoder:aDecoder]; + if(self != nil) { + [self initProperties]; + } + return self; +} + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if(self != nil) { + [self initProperties]; + } + return self; +} + +- (void)drawRect:(CGRect)rect { + [self drawTrack]; + [self drawThumb]; +} + +#pragma mark TGPDiscreteSlider7 + +- (void)initProperties { + _tickStyle = ComponentStyleRectangular; + _tickSize = (CGSize) {1.0, 4.0}; + _tickCount = 11; + _trackStyle = ComponentStyleIOS; + _trackThickness = 2.0; + _thumbStyle = ComponentStyleIOS; + _thumbSize = (CGSize) {10.0, 10.0}; + _thumbColor = [UIColor lightGrayColor]; + _thumbShadowRadius = 0.0; + _thumbShadowOffset = CGSizeZero; + _intMinimumValue = -5; + _incrementValue = 1; + _intValue = 0; + _ticksAbscisses = [NSMutableArray array]; + _thumbAbscisse = 0.0; + _trackRectangle = CGRectZero; + + // In case we need a colored track, initialize it now + // There may be a more elegant way to do this than with a CALayer, + // but then again CALayer brings free animation and will animate along the thumb + _colorTrackLayer = [CALayer layer]; + _colorTrackLayer.backgroundColor = [[UIColor colorWithHue:211.0/360.0 saturation:1 brightness:1 alpha:1] CGColor]; + _colorTrackLayer.cornerRadius = 2.0; + [self.layer addSublayer:self.colorTrackLayer]; + + // The thumb is its own CALayer, which brings in free animation + _thumbLayer = [CALayer layer]; + [self.layer addSublayer:self.thumbLayer]; + + self.multipleTouchEnabled = NO; + [self layoutTrack]; +} + +- (void)drawTrack { + const CGContextRef ctx = UIGraphicsGetCurrentContext(); + + // Track + switch(self.trackStyle) { + case ComponentStyleRectangular: + CGContextAddRect(ctx, self.trackRectangle); + break; + + + case ComponentStyleInvisible: + // Nothing to draw + break; + + case ComponentStyleRounded: + case ComponentStyleIOS: + default: { + UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:self.trackRectangle + cornerRadius:self.trackRectangle.size.height/2]; + CGContextAddPath(ctx, [path CGPath]) ; + break; + } + } + + // Ticks + if(ComponentStyleIOS != self.tickStyle) { + NSAssert(nil != self.ticksAbscisses, @"ticksAbscisses"); + if(nil != self.ticksAbscisses) { + + for(NSValue * originValue in self.ticksAbscisses) { + CGPoint originPoint = [originValue CGPointValue]; + CGRect rectangle = CGRectMake(originPoint.x-(self.tickSize.width/2), + originPoint.y-(self.tickSize.height/2), + self.tickSize.width, self.tickSize.height); + switch(self.tickStyle) { + case ComponentStyleRounded: { + UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:rectangle + cornerRadius:rectangle.size.height/2]; + CGContextAddPath(ctx, [path CGPath]) ; + break; + } + + case ComponentStyleRectangular: + CGContextAddRect(ctx, rectangle); + break; + + case ComponentStyleInvisible: + case ComponentStyleIOS: + default: + // Nothing to draw + break; + } + } + } + } // iOS UISlider does not have ticks + + CGContextSetFillColor(ctx, CGColorGetComponents([self.tintColor CGColor])); + CGContextFillPath(ctx); + + // For colored track, we overlay a CALayer, which will animate along with the cursor + if(ComponentStyleIOS == self.trackStyle) { + self.colorTrackLayer.frame = ({ + CGRect frame = self.trackRectangle; + frame.size.width = self.thumbAbscisse - CGRectGetMinX(self.trackRectangle); + frame; + }); + } else { + self.colorTrackLayer.frame = CGRectZero; + } +} + +- (void)drawThumb { + const CGSize thumbSizeForStyle = [self thumbSizeIncludingShadow]; + const CGFloat thumbWidth = thumbSizeForStyle.width; + const CGFloat thumbHeight = thumbSizeForStyle.height; + const CGRect rectangle = CGRectMake(self.thumbAbscisse - (thumbWidth / 2), + (self.frame.size.height - thumbHeight)/2, + thumbWidth, + thumbHeight); + + const CGFloat shadowRadius = ((self.thumbStyle == ComponentStyleIOS) + ? iOSThumbShadowRadius + : self.thumbShadowRadius); + const CGSize shadowOffset = ((self.thumbStyle == ComponentStyleIOS) + ? iosThumbShadowOffset + : self.thumbShadowOffset); + + self.thumbLayer.frame = ((shadowRadius != 0.0) // Ignore offset if there is no shadow + ? CGRectInset(rectangle, + shadowRadius + shadowOffset.width, + shadowRadius + shadowOffset.height) + : CGRectInset(rectangle, shadowRadius, shadowRadius)); + + switch(self.thumbStyle) { + case ComponentStyleRounded: // A rounded thumb is circular + self.thumbLayer.backgroundColor = [self.thumbColor CGColor]; + self.thumbLayer.borderColor = [[UIColor clearColor] CGColor]; + self.thumbLayer.borderWidth = 0.0; + self.thumbLayer.cornerRadius = self.thumbLayer.frame.size.width/2; + self.thumbLayer.allowsEdgeAntialiasing = YES; + break; + + case ComponentStyleRectangular: + self.thumbLayer.backgroundColor = [self.thumbColor CGColor]; + self.thumbLayer.borderColor = [[UIColor clearColor] CGColor]; + self.thumbLayer.borderWidth = 0.0; + self.thumbLayer.cornerRadius = 0.0; + self.thumbLayer.allowsEdgeAntialiasing = NO; + break; + + case ComponentStyleInvisible: + self.thumbLayer.backgroundColor = [[UIColor clearColor] CGColor]; + self.thumbLayer.cornerRadius = 0.0; + break; + + case ComponentStyleIOS: + default: + self.thumbLayer.backgroundColor = [[UIColor whiteColor] CGColor]; + self.thumbLayer.borderColor = [[UIColor colorWithHue:0 saturation: 0 brightness: 0.8 alpha: 1] + CGColor]; + self.thumbLayer.borderWidth = 0.5; + self.thumbLayer.cornerRadius = self.thumbLayer.frame.size.width/2; + self.thumbLayer.allowsEdgeAntialiasing = YES; + break; + } + + // Shadow + if(shadowRadius != 0.0) { +#if TARGET_INTERFACE_BUILDER + self.thumbLayer.shadowOffset = CGSizeMake(shadowOffset.width, -shadowOffset.height); +#else // !TARGET_INTERFACE_BUILDER + self.thumbLayer.shadowOffset = shadowOffset; +#endif // TARGET_INTERFACE_BUILDER + + self.thumbLayer.shadowRadius = shadowRadius; + self.thumbLayer.shadowColor = [[UIColor blackColor] CGColor]; + self.thumbLayer.shadowOpacity = 0.15; + } else { + self.thumbLayer.shadowRadius = 0.0; + self.thumbLayer.shadowOffset = CGSizeZero; + self.thumbLayer.shadowColor = [[UIColor clearColor] CGColor]; + self.thumbLayer.shadowOpacity = 0.0; + } +} + +- (void)layoutTrack { + NSAssert1(self.tickCount > 1, @"2 ticks minimum %d", self.tickCount); + const unsigned int segments = MAX(1, self.tickCount - 1); + const CGFloat thumbWidth = [self thumbSizeIncludingShadow].width; + const double trackLength = (double)(self.frame.size.width - thumbWidth); + + // Calculate the track ticks positions + const CGFloat trackHeight = ((ComponentStyleIOS == self.trackStyle) + ? 2.0 + : self.trackThickness); + self.trackRectangle = CGRectMake(thumbWidth/2, (self.frame.size.height - trackHeight)/2, + self.frame.size.width - thumbWidth, trackHeight); + const CGFloat trackY = self.frame.size.height / 2; + [self.ticksAbscisses removeAllObjects]; + for( int iterate = 0; iterate <= segments; iterate++) { + const double ratio = (double)iterate / (double)segments; + const CGFloat originX = (thumbWidth / 2) + (CGFloat)(trackLength * ratio); + [self.ticksAbscisses addObject: [NSValue valueWithCGPoint:CGPointMake(originX, trackY)]]; + } + + [self layoutThumb]; +} + +- (void)layoutThumb { + NSAssert1(self.tickCount > 1, @"2 ticks minimum %d", self.tickCount); + const unsigned int segments = MAX(1, self.tickCount - 1); + const CGFloat thumbWidth = [self thumbSizeIncludingShadow].width; + const double trackLength = (double)(self.frame.size.width - thumbWidth); + + // Calculate the thumb position + const unsigned int nonZeroIncrement = ((0 == self.incrementValue) ? 1 : self.incrementValue); + double thumbRatio = (double)(self.value - self.minimumValue) / (double)(segments * nonZeroIncrement); + thumbRatio = MAX(0.0, MIN(thumbRatio, 1.0)); // Normalized + self.thumbAbscisse = (thumbWidth / 2) + (CGFloat)(trackLength * thumbRatio); +} + +- (CGSize)thumbSizeIncludingShadow { + switch (self.thumbStyle) { + case ComponentStyleInvisible: + return CGSizeZero; + + case ComponentStyleRectangular: + case ComponentStyleRounded: + return ((self.thumbShadowRadius != 0.0) + ? CGSizeMake(self.thumbSize.width + + (self.thumbShadowRadius * 2) + + (self.thumbShadowOffset.width * 2), + self.thumbSize.height + + (self.thumbShadowRadius * 2) + + (self.thumbShadowOffset.height * 2)) + : self.thumbSize); + + case ComponentStyleIOS: + return CGSizeMake(33.0 + + (iOSThumbShadowRadius * 2) + + (iosThumbShadowOffset.width * 2), + 33.0 + + (iOSThumbShadowRadius * 2) + + (iosThumbShadowOffset.height * 2)); + + default: + return (CGSize){33.0, 33.0}; + } +} + +#pragma mark UIControl + +- (void)sendActionsForControlEvents { + // Interface builder hides the IBInspectable for UIControl +#if !TARGET_INTERFACE_BUILDER + [self sendActionsForControlEvents:UIControlEventValueChanged]; +#endif // !TARGET_INTERFACE_BUILDER +} + +#pragma mark Touches + +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { + [self touchDown:touches duration:0.1]; +} + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { + [self touchDown:touches duration:0.0]; +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { + [self touchUp:touches]; +} + +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { + [self touchUp:touches]; +} + +- (void)touchDown:(NSSet *)touches duration:(NSTimeInterval)duration { + const UITouch * touch = [touches anyObject]; + if(nil != touch) { + const CGPoint location = [touch locationInView:touch.view]; + [self moveThumbTo:location.x duration:duration]; + } +} + +- (void)touchUp:(NSSet *)touches { + const UITouch * touch = [touches anyObject]; + if(nil != touch) { + const CGPoint location = [touch locationInView:touch.view]; + const unsigned int tick = [self pickTickFromSliderPosition:location.x]; + [self moveThumbToTick:tick]; + } +} + +#pragma mark Notifications + +- (void)moveThumbToTick:(unsigned int)tick { + const unsigned int nonZeroIncrement = ((0 == self.incrementValue) ? 1 : self.incrementValue); + _intValue = self.minimumValue + (tick * nonZeroIncrement); + [self sendActionsForControlEvents]; + + [self layoutThumb]; + [self setNeedsDisplay]; +} + +- (void)moveThumbTo:(CGFloat)abscisse duration:(CFTimeInterval)duration { + const CGFloat leftMost = CGRectGetMinX(self.trackRectangle); + const CGFloat rightMost = CGRectGetMaxX(self.trackRectangle); + + self.thumbAbscisse = MAX(leftMost, MIN(abscisse, rightMost)); + [CATransaction setAnimationDuration:duration]; + + const unsigned int tick = [self pickTickFromSliderPosition:self.thumbAbscisse]; + const unsigned int nonZeroIncrement = ((0 == self.incrementValue) ? 1 : self.incrementValue); + _intValue = self.minimumValue + (tick * nonZeroIncrement); + [self sendActionsForControlEvents]; + + [self setNeedsDisplay]; +} + +- (unsigned int)pickTickFromSliderPosition:(CGFloat)abscisse { + const CGFloat leftMost = CGRectGetMinX(self.trackRectangle); + const CGFloat rightMost = CGRectGetMaxX(self.trackRectangle); + const CGFloat clampedAbscisse = MAX(leftMost, MIN(abscisse, rightMost)); + const double ratio = (double)(clampedAbscisse - leftMost) / (double)(rightMost - leftMost); + const unsigned int segments = MAX(1, self.tickCount - 1); + return (unsigned int) round( (double)segments * ratio); +} + +#pragma mark - Interface Builder + +#if TARGET_INTERFACE_BUILDER +// Interface builder hides the IBInspectable for UIControl +- (void)addTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents {} +#endif // TARGET_INTERFACE_BUILDER + +@end diff --git a/TGPControlsTests/Info.plist b/TGPControlsTests/Info.plist new file mode 100644 index 0000000..f794cd2 --- /dev/null +++ b/TGPControlsTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.thegothicparty.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/TGPControls_License.txt b/TGPControls_License.txt new file mode 100644 index 0000000..91b666c --- /dev/null +++ b/TGPControls_License.txt @@ -0,0 +1,19 @@ +Copyright (c) 2014 Xavier Schott + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file