diff --git a/TimesSquare-iOS/Info.plist b/TimesSquare-iOS/Info.plist new file mode 100644 index 0000000..f8785d5 --- /dev/null +++ b/TimesSquare-iOS/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + LSApplicationCategoryType + + NSPrincipalClass + + UIAppFonts + + + + + diff --git a/TimesSquare.podspec b/TimesSquare.podspec index bd01683..694f594 100644 --- a/TimesSquare.podspec +++ b/TimesSquare.podspec @@ -9,4 +9,4 @@ Pod::Spec.new do |s| s.platform = :ios, '5.0' s.source_files = 'TimesSquare/*.{h,m}' s.requires_arc = true -end \ No newline at end of file +end diff --git a/TimesSquare.xcodeproj/project.pbxproj b/TimesSquare.xcodeproj/project.pbxproj index bd77b35..acf3560 100644 --- a/TimesSquare.xcodeproj/project.pbxproj +++ b/TimesSquare.xcodeproj/project.pbxproj @@ -7,6 +7,26 @@ objects = { /* Begin PBXBuildFile section */ + 5C4733B21AC357A700269A66 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A806809E167012980071C71E /* CoreGraphics.framework */; }; + 5C4733B31AC357AB00269A66 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A806805E16700FD70071C71E /* Foundation.framework */; }; + 5C4733B41AC357B200269A66 /* TimesSquare.h in Headers */ = {isa = PBXBuildFile; fileRef = A806806316700FD70071C71E /* TimesSquare.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5C4733B51AC357B200269A66 /* TSQCalendarCell.h in Headers */ = {isa = PBXBuildFile; fileRef = A8068086167010030071C71E /* TSQCalendarCell.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5C4733B61AC357B200269A66 /* TSQCalendarMonthHeaderCell.h in Headers */ = {isa = PBXBuildFile; fileRef = A8068088167010030071C71E /* TSQCalendarMonthHeaderCell.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5C4733B71AC357B200269A66 /* TSQCalendarRowCell.h in Headers */ = {isa = PBXBuildFile; fileRef = A806808A167010030071C71E /* TSQCalendarRowCell.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5C4733B81AC357B200269A66 /* TSQCalendarView.h in Headers */ = {isa = PBXBuildFile; fileRef = A806808C167010030071C71E /* TSQCalendarView.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5C4733B91AC357C000269A66 /* TSQCalendarCell.m in Sources */ = {isa = PBXBuildFile; fileRef = A8068087167010030071C71E /* TSQCalendarCell.m */; }; + 5C4733BA1AC357C000269A66 /* TSQCalendarMonthHeaderCell.m in Sources */ = {isa = PBXBuildFile; fileRef = A8068089167010030071C71E /* TSQCalendarMonthHeaderCell.m */; }; + 5C4733BB1AC357C000269A66 /* TSQCalendarRowCell.m in Sources */ = {isa = PBXBuildFile; fileRef = A806808B167010030071C71E /* TSQCalendarRowCell.m */; }; + 5C4733BC1AC357C000269A66 /* TSQCalendarView.m in Sources */ = {isa = PBXBuildFile; fileRef = A806808D167010030071C71E /* TSQCalendarView.m */; }; + 5CEF2FEE1ACB788F003E8F61 /* TimesSquare.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = A806806316700FD70071C71E /* TimesSquare.h */; }; + 5CEF2FEF1ACB788F003E8F61 /* TSQCalendarCell.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = A8068086167010030071C71E /* TSQCalendarCell.h */; }; + 5CEF2FF01ACB788F003E8F61 /* TSQCalendarMonthHeaderCell.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = A8068088167010030071C71E /* TSQCalendarMonthHeaderCell.h */; }; + 5CEF2FF11ACB788F003E8F61 /* TSQCalendarRowCell.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = A806808A167010030071C71E /* TSQCalendarRowCell.h */; }; + 5CEF2FF21ACB788F003E8F61 /* TSQCalendarView.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = A806808C167010030071C71E /* TSQCalendarView.h */; }; + 87F195FB1B61B2D100EC341E /* TSQCalendarDayButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F195F91B61B2D100EC341E /* TSQCalendarDayButton.h */; }; + 87F195FC1B61B2D100EC341E /* TSQCalendarDayButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F195F91B61B2D100EC341E /* TSQCalendarDayButton.h */; }; + 87F195FD1B61B2D100EC341E /* TSQCalendarDayButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F195FA1B61B2D100EC341E /* TSQCalendarDayButton.m */; }; + 87F195FE1B61B2D100EC341E /* TSQCalendarDayButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F195FA1B61B2D100EC341E /* TSQCalendarDayButton.m */; }; A806805F16700FD70071C71E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A806805E16700FD70071C71E /* Foundation.framework */; }; A806808E167010030071C71E /* TSQCalendarCell.m in Sources */ = {isa = PBXBuildFile; fileRef = A8068087167010030071C71E /* TSQCalendarCell.m */; }; A8068090167010030071C71E /* TSQCalendarMonthHeaderCell.m in Sources */ = {isa = PBXBuildFile; fileRef = A8068089167010030071C71E /* TSQCalendarMonthHeaderCell.m */; }; @@ -21,6 +41,20 @@ /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ + 5CEF2FED1ACB785C003E8F61 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = Headers; + dstSubfolderSpec = 1; + files = ( + 5CEF2FEE1ACB788F003E8F61 /* TimesSquare.h in CopyFiles */, + 5CEF2FEF1ACB788F003E8F61 /* TSQCalendarCell.h in CopyFiles */, + 5CEF2FF01ACB788F003E8F61 /* TSQCalendarMonthHeaderCell.h in CopyFiles */, + 5CEF2FF11ACB788F003E8F61 /* TSQCalendarRowCell.h in CopyFiles */, + 5CEF2FF21ACB788F003E8F61 /* TSQCalendarView.h in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; A806805916700FD70071C71E /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -33,6 +67,10 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 5C4733991AC3575B00269A66 /* TimesSquare.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TimesSquare.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 5C47339C1AC3575C00269A66 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 87F195F91B61B2D100EC341E /* TSQCalendarDayButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSQCalendarDayButton.h; sourceTree = ""; }; + 87F195FA1B61B2D100EC341E /* TSQCalendarDayButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSQCalendarDayButton.m; sourceTree = ""; }; A806805B16700FD70071C71E /* libTimesSquare.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libTimesSquare.a; sourceTree = BUILT_PRODUCTS_DIR; }; A806805E16700FD70071C71E /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; A806806216700FD70071C71E /* TimesSquare-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TimesSquare-Prefix.pch"; sourceTree = ""; }; @@ -51,6 +89,15 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 5C4733951AC3575B00269A66 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5C4733B31AC357AB00269A66 /* Foundation.framework in Frameworks */, + 5C4733B21AC357A700269A66 /* CoreGraphics.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; A806805816700FD70071C71E /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -63,10 +110,27 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 5C47339A1AC3575C00269A66 /* TimesSquare-iOS */ = { + isa = PBXGroup; + children = ( + 5C47339B1AC3575C00269A66 /* Supporting Files */, + ); + path = "TimesSquare-iOS"; + sourceTree = ""; + }; + 5C47339B1AC3575C00269A66 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 5C47339C1AC3575C00269A66 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; A806805016700FD70071C71E = { isa = PBXGroup; children = ( A806806016700FD70071C71E /* TimesSquare */, + 5C47339A1AC3575C00269A66 /* TimesSquare-iOS */, A806805D16700FD70071C71E /* Frameworks */, A806805C16700FD70071C71E /* Products */, ); @@ -76,6 +140,7 @@ isa = PBXGroup; children = ( A806805B16700FD70071C71E /* libTimesSquare.a */, + 5C4733991AC3575B00269A66 /* TimesSquare.framework */, ); name = Products; sourceTree = ""; @@ -102,6 +167,8 @@ A806808A167010030071C71E /* TSQCalendarRowCell.h */, A806808B167010030071C71E /* TSQCalendarRowCell.m */, A806808C167010030071C71E /* TSQCalendarView.h */, + 87F195F91B61B2D100EC341E /* TSQCalendarDayButton.h */, + 87F195FA1B61B2D100EC341E /* TSQCalendarDayButton.m */, A806808D167010030071C71E /* TSQCalendarView.m */, A806806116700FD70071C71E /* Supporting Files */, ); @@ -119,12 +186,26 @@ /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ + 5C4733961AC3575B00269A66 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 5C4733B41AC357B200269A66 /* TimesSquare.h in Headers */, + 5C4733B81AC357B200269A66 /* TSQCalendarView.h in Headers */, + 87F195FC1B61B2D100EC341E /* TSQCalendarDayButton.h in Headers */, + 5C4733B51AC357B200269A66 /* TSQCalendarCell.h in Headers */, + 5C4733B61AC357B200269A66 /* TSQCalendarMonthHeaderCell.h in Headers */, + 5C4733B71AC357B200269A66 /* TSQCalendarRowCell.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; EFD8DE6B167AF77100F87FBE /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( EFD8DE6D167AF77B00F87FBE /* TimesSquare.h in Headers */, EFD8DE6E167AF78100F87FBE /* TSQCalendarCell.h in Headers */, + 87F195FB1B61B2D100EC341E /* TSQCalendarDayButton.h in Headers */, EFD8DE6F167AF78600F87FBE /* TSQCalendarMonthHeaderCell.h in Headers */, EFD8DE70167AF78C00F87FBE /* TSQCalendarRowCell.h in Headers */, EFD8DE71167AF79000F87FBE /* TSQCalendarView.h in Headers */, @@ -151,6 +232,25 @@ /* End PBXLegacyTarget section */ /* Begin PBXNativeTarget section */ + 5C4733981AC3575B00269A66 /* TimesSquare-iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5C4733B01AC3575C00269A66 /* Build configuration list for PBXNativeTarget "TimesSquare-iOS" */; + buildPhases = ( + 5C4733941AC3575B00269A66 /* Sources */, + 5C4733951AC3575B00269A66 /* Frameworks */, + 5C4733961AC3575B00269A66 /* Headers */, + 5C4733971AC3575B00269A66 /* Resources */, + 5CEF2FED1ACB785C003E8F61 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "TimesSquare-iOS"; + productName = "TimesSquare-iOS"; + productReference = 5C4733991AC3575B00269A66 /* TimesSquare.framework */; + productType = "com.apple.product-type.framework"; + }; A806805A16700FD70071C71E /* TimesSquare */ = { isa = PBXNativeTarget; buildConfigurationList = A806808016700FD80071C71E /* Build configuration list for PBXNativeTarget "TimesSquare" */; @@ -175,14 +275,20 @@ A806805216700FD70071C71E /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0640; + LastUpgradeCheck = 1320; ORGANIZATIONNAME = Square; + TargetAttributes = { + 5C4733981AC3575B00269A66 = { + CreatedOnToolsVersion = 6.2; + }; + }; }; buildConfigurationList = A806805516700FD70071C71E /* Build configuration list for PBXProject "TimesSquare" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, ); mainGroup = A806805016700FD70071C71E; @@ -192,17 +298,41 @@ targets = ( A806805A16700FD70071C71E /* TimesSquare */, A81E05F71682A0E000E79A2B /* TimesSquare Documentation */, + 5C4733981AC3575B00269A66 /* TimesSquare-iOS */, ); }; /* End PBXProject section */ +/* Begin PBXResourcesBuildPhase section */ + 5C4733971AC3575B00269A66 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ + 5C4733941AC3575B00269A66 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5C4733B91AC357C000269A66 /* TSQCalendarCell.m in Sources */, + 5C4733BA1AC357C000269A66 /* TSQCalendarMonthHeaderCell.m in Sources */, + 87F195FE1B61B2D100EC341E /* TSQCalendarDayButton.m in Sources */, + 5C4733BB1AC357C000269A66 /* TSQCalendarRowCell.m in Sources */, + 5C4733BC1AC357C000269A66 /* TSQCalendarView.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; A806805716700FD70071C71E /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( A806808E167010030071C71E /* TSQCalendarCell.m in Sources */, A8068090167010030071C71E /* TSQCalendarMonthHeaderCell.m in Sources */, + 87F195FD1B61B2D100EC341E /* TSQCalendarDayButton.m in Sources */, A8068092167010030071C71E /* TSQCalendarRowCell.m in Sources */, A8068094167010030071C71E /* TSQCalendarView.m in Sources */, ); @@ -211,29 +341,135 @@ /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ + 5C4733AC1AC3575C00269A66 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + 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_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + 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; + INFOPLIST_FILE = "TimesSquare-iOS/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.square.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = TimesSquare; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 5C4733AD1AC3575C00269A66 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + 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_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = 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; + INFOPLIST_FILE = "TimesSquare-iOS/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = "com.square.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = TimesSquare; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; A806807E16700FD80071C71E /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + 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_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_TREAT_WARNINGS_AS_ERRORS = YES; + GCC_TREAT_WARNINGS_AS_ERRORS = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_PEDANTIC = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 5.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; ONLY_ACTIVE_ARCH = YES; PUBLIC_HEADERS_FOLDER_PATH = "include/$(PRODUCT_NAME)"; RUN_CLANG_STATIC_ANALYZER = YES; @@ -245,18 +481,40 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_TREAT_WARNINGS_AS_ERRORS = YES; + GCC_NO_COMMON_BLOCKS = YES; + GCC_TREAT_WARNINGS_AS_ERRORS = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_PEDANTIC = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 5.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; PUBLIC_HEADERS_FOLDER_PATH = "include/$(PRODUCT_NAME)"; RUN_CLANG_STATIC_ANALYZER = YES; SDKROOT = iphoneos; @@ -293,6 +551,7 @@ A81E05F91682A0E000E79A2B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_OBJC_WEAK = YES; DEBUGGING_SYMBOLS = YES; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; @@ -309,6 +568,7 @@ A81E05FA1682A0E000E79A2B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_OBJC_WEAK = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -323,6 +583,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 5C4733B01AC3575C00269A66 /* Build configuration list for PBXNativeTarget "TimesSquare-iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5C4733AC1AC3575C00269A66 /* Debug */, + 5C4733AD1AC3575C00269A66 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; A806805516700FD70071C71E /* Build configuration list for PBXProject "TimesSquare" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/TimesSquare.xcodeproj/xcshareddata/xcschemes/TimesSquare Documentation.xcscheme b/TimesSquare.xcodeproj/xcshareddata/xcschemes/TimesSquare Documentation.xcscheme index 9366e66..b461875 100644 --- a/TimesSquare.xcodeproj/xcshareddata/xcschemes/TimesSquare Documentation.xcscheme +++ b/TimesSquare.xcodeproj/xcshareddata/xcschemes/TimesSquare Documentation.xcscheme @@ -1,6 +1,6 @@ + shouldUseLaunchSchemeArgsEnv = "YES"> - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TimesSquare.xcodeproj/xcshareddata/xcschemes/TimesSquare.xcscheme b/TimesSquare.xcodeproj/xcshareddata/xcschemes/TimesSquare.xcscheme index ad1cc5d..99fc9d6 100644 --- a/TimesSquare.xcodeproj/xcshareddata/xcschemes/TimesSquare.xcscheme +++ b/TimesSquare.xcodeproj/xcshareddata/xcschemes/TimesSquare.xcscheme @@ -1,6 +1,6 @@ + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -41,13 +41,14 @@ - - + +typedef NS_ENUM(NSInteger, CalendarButtonType) { + CalendarButtonTypeNormalDay = 0, + CalendarButtonTypeOtherMonth = 1, + CalendarButtonTypeSelected = 2, + CalendarButtonTypeInitialDay = 3, +}; + +@interface TSQCalendarDayButton : UIButton + +@property (nonatomic, assign) CalendarButtonType type; +@property (nonatomic, strong) NSDate *day; + +@property (nonatomic, strong) UILabel *tsqSubtitleLabel; +@property (nonatomic, strong) UILabel *subtitleSymbolLabel; +@property (nonatomic, strong) UIImageView *iconImageView; + +@property (nonatomic, assign) BOOL isInitialDay; + +- (BOOL)isForToday; +- (BOOL)isForDay:(NSDate *)date; + +@end diff --git a/TimesSquare/TSQCalendarDayButton.m b/TimesSquare/TSQCalendarDayButton.m new file mode 100644 index 0000000..d10a034 --- /dev/null +++ b/TimesSquare/TSQCalendarDayButton.m @@ -0,0 +1,208 @@ +// +// TSQCalendarDayButton.m +// TimesSquare +// +// Created by Loretta Chan on 7/23/15. +// Copyright (c) 2015 Square. All rights reserved. +// + +#import "TSQCalendarDayButton.h" + +@implementation TSQCalendarDayButton + +static const CGFloat TSQCalendarRowCellMaxSubtitleHeight = 18.0f; +static const CGFloat TSQCalendarRowCellSubtitleBuffer = 15.0f; + +- (instancetype)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + // default button type to normal day + self.type = CalendarButtonTypeNormalDay; + + [self setTitleEdgeInsets:UIEdgeInsetsMake(-10, 0, 0, 0)]; + self.tsqSubtitleLabel = [[UILabel alloc] initWithFrame:CGRectZero]; + self.tsqSubtitleLabel.textAlignment = NSTextAlignmentCenter; + self.tsqSubtitleLabel.userInteractionEnabled = NO; + self.tsqSubtitleLabel.adjustsFontSizeToFitWidth = NO; + self.tsqSubtitleLabel.lineBreakMode = NSLineBreakByTruncatingTail; + [self addSubview:self.tsqSubtitleLabel]; + + self.subtitleSymbolLabel = [[UILabel alloc] initWithFrame:CGRectZero]; + self.subtitleSymbolLabel.userInteractionEnabled = NO; + [self addSubview:self.subtitleSymbolLabel]; + + self.iconImageView = [[UIImageView alloc] initWithFrame:CGRectZero]; + self.iconImageView.userInteractionEnabled = NO; + [self addSubview:self.iconImageView]; + + [self registerForNotifications]; + } + return self; +} + +- (void)dealloc +{ + [self unregisterForNotifications]; +} + +- (void)layoutSubviews +{ + [super layoutSubviews]; + + CGFloat midX = CGRectGetMidX(self.bounds); + CGFloat subtitleCenterY = 0.75f * CGRectGetMaxY(self.bounds); + + self.tsqSubtitleLabel.hidden = !([self.tsqSubtitleLabel.text length] > 0); + self.subtitleSymbolLabel.hidden = !([self.subtitleSymbolLabel.text length] > 0); + self.iconImageView.hidden = (self.iconImageView.image == nil); + + CGFloat iconWidth = self.iconImageView.image.size.width; + CGFloat iconHeight = self.iconImageView.image.size.height; + + if (! self.tsqSubtitleLabel.hidden) + { + CGSize maxSubtitleSize = [self maxSubtitleSize]; + CGSize sizeThatFits = [self.tsqSubtitleLabel sizeThatFits:maxSubtitleSize]; + + CGFloat subtitleWidth = fminf(sizeThatFits.width, maxSubtitleSize.width); + CGFloat subtitleHeight = fminf(sizeThatFits.height, maxSubtitleSize.height); + CGFloat originX = midX - subtitleWidth/2.0f; + CGFloat originY = subtitleCenterY - subtitleHeight/2.0f; + + CGRect subtitleFrame = CGRectMake(floorf(originX), + floorf(originY), + subtitleWidth, + subtitleHeight); + self.tsqSubtitleLabel.frame = CGRectIntegral(subtitleFrame); + } + + if (! self.subtitleSymbolLabel.hidden) + { + CGSize maxSymbolSize = [self maxSubtitleSymbolSize]; + CGSize sizeThatFits = [self.subtitleSymbolLabel sizeThatFits:maxSymbolSize]; + + CGFloat symbolWidth = fminf(sizeThatFits.width, maxSymbolSize.width); + CGFloat symbolHeight = fminf(sizeThatFits.height, maxSymbolSize.height); + CGFloat originX = CGRectGetMaxX(self.bounds) - TSQCalendarRowCellSubtitleBuffer; + CGFloat originY = subtitleCenterY - symbolHeight/2.0f; + + if (! self.tsqSubtitleLabel.hidden) + { + // when subtitle is showing, shift symbol to right of subtitle + CGFloat symbolBuffer = (TSQCalendarRowCellSubtitleBuffer - symbolWidth)/2.0f; + originX = CGRectGetMaxX(self.tsqSubtitleLabel.frame) + symbolBuffer; + } + + CGRect symbolFrame = CGRectMake(floorf(originX), + floorf(originY), + symbolWidth, + symbolHeight); + self.subtitleSymbolLabel.frame = CGRectIntegral(symbolFrame); + } + + if (! self.iconImageView.hidden) + { + CGFloat midX = CGRectGetMidX(self.bounds); + + CGFloat originX = midX - iconWidth/2.0f; + CGFloat originY = subtitleCenterY - iconHeight/2.0f; + + if (! self.tsqSubtitleLabel.hidden) + { + // when subtitle is showing, shift icon to left of subtitle + CGFloat iconBuffer = (TSQCalendarRowCellSubtitleBuffer - iconWidth)/2.0f; + originX = self.tsqSubtitleLabel.frame.origin.x - iconWidth - iconBuffer; + } + + CGRect iconFrame = CGRectMake(floorf(originX), + floorf(originY), + iconWidth, + iconHeight); + self.iconImageView.frame = CGRectIntegral(iconFrame); + } +} + +#pragma mark - Observations + +- (void)registerForNotifications +{ + [self.tsqSubtitleLabel addObserver:self + forKeyPath:@"font" + options:NSKeyValueObservingOptionNew + context:nil]; + + [self.tsqSubtitleLabel addObserver:self + forKeyPath:@"text" + options:NSKeyValueObservingOptionNew + context:nil]; + + [self.subtitleSymbolLabel addObserver:self + forKeyPath:@"font" + options:NSKeyValueObservingOptionNew + context:nil]; + + [self.subtitleSymbolLabel addObserver:self + forKeyPath:@"text" + options:NSKeyValueObservingOptionNew + context:nil]; + + [self.iconImageView addObserver:self + forKeyPath:@"image" + options:NSKeyValueObservingOptionNew + context:nil]; +} + +- (void)unregisterForNotifications +{ + [self.tsqSubtitleLabel removeObserver:self forKeyPath:@"font"]; + [self.tsqSubtitleLabel removeObserver:self forKeyPath:@"text"]; + [self.subtitleSymbolLabel removeObserver:self forKeyPath:@"font"]; + [self.subtitleSymbolLabel removeObserver:self forKeyPath:@"text"]; + [self.iconImageView removeObserver:self forKeyPath:@"image"]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + // relayout subviews when certain properties of the subtitle label, subtitle symbol label, or icon image view changes + [self layoutSubviews]; +} + +#pragma mark - Helper Methods + +- (CGSize)maxSubtitleSize +{ + CGFloat maxWidth = self.bounds.size.width - 2 * TSQCalendarRowCellSubtitleBuffer; + return CGSizeMake(maxWidth, TSQCalendarRowCellMaxSubtitleHeight); +} + +- (CGSize)maxSubtitleSymbolSize +{ + return CGSizeMake(8.0f, TSQCalendarRowCellMaxSubtitleHeight); +} + +- (BOOL)isForToday +{ + NSDate *today = [NSDate date]; + return [self isForDayIgnoringTime:today]; +} + +- (BOOL)isForDay:(NSDate *)day +{ + return [self isForDayIgnoringTime:day]; +} + +- (BOOL)isForDayIgnoringTime:(NSDate *)aDay +{ + if (aDay == nil || self.day == nil) { + return NO; + } + + NSDateComponents *components1 = [[NSCalendar currentCalendar] components:(NSCalendarUnitYear| NSCalendarUnitMonth | NSCalendarUnitDay) fromDate:self.day]; + NSDateComponents *components2 = [[NSCalendar currentCalendar] components:(NSCalendarUnitYear| NSCalendarUnitMonth | NSCalendarUnitDay) fromDate:aDay]; + return ((components1.year == components2.year) && + (components1.month == components2.month) && + (components1.day == components2.day)); +} + +@end diff --git a/TimesSquare/TSQCalendarMonthHeaderCell.h b/TimesSquare/TSQCalendarMonthHeaderCell.h index d51838b..7a7e3f3 100644 --- a/TimesSquare/TSQCalendarMonthHeaderCell.h +++ b/TimesSquare/TSQCalendarMonthHeaderCell.h @@ -15,6 +15,8 @@ */ @interface TSQCalendarMonthHeaderCell : TSQCalendarCell +@property (nonatomic, strong) NSDateFormatter *monthDateFormatter; + /** @name Day Labels */ /** The day header labels. diff --git a/TimesSquare/TSQCalendarMonthHeaderCell.m b/TimesSquare/TSQCalendarMonthHeaderCell.m index 776f692..763051d 100644 --- a/TimesSquare/TSQCalendarMonthHeaderCell.m +++ b/TimesSquare/TSQCalendarMonthHeaderCell.m @@ -15,14 +15,14 @@ @interface TSQCalendarMonthHeaderCell () -@property (nonatomic, strong) NSDateFormatter *monthDateFormatter; + @end @implementation TSQCalendarMonthHeaderCell -- (id)initWithCalendar:(NSCalendar *)calendar reuseIdentifier:(NSString *)reuseIdentifier; +- (id)initWithCalendar:(NSCalendar *)calendar reuseIdentifier:(NSString *)reuseIdentifier { self = [super initWithCalendar:calendar reuseIdentifier:reuseIdentifier]; if (!self) { @@ -68,9 +68,9 @@ - (void)createHeaderLabels; } for (NSUInteger index = 0; index < self.daysInWeek; index++) { - NSInteger ordinality = [self.calendar ordinalityOfUnit:NSDayCalendarUnit inUnit:NSWeekCalendarUnit forDate:referenceDate]; + NSInteger ordinality = [self.calendar ordinalityOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitWeekOfMonth forDate:referenceDate]; UILabel *label = [[UILabel alloc] initWithFrame:self.frame]; - label.textAlignment = UITextAlignmentCenter; + label.textAlignment = NSTextAlignmentCenter; label.text = [dayFormatter stringFromDate:referenceDate]; label.font = [UIFont boldSystemFontOfSize:12.f]; label.backgroundColor = self.backgroundColor; @@ -85,7 +85,7 @@ - (void)createHeaderLabels; } self.headerLabels = headerLabels; - self.textLabel.textAlignment = UITextAlignmentCenter; + self.textLabel.textAlignment = NSTextAlignmentCenter; self.textLabel.textColor = self.textColor; self.textLabel.shadowColor = [UIColor whiteColor]; self.textLabel.shadowOffset = self.shadowOffset; diff --git a/TimesSquare/TSQCalendarRowCell.h b/TimesSquare/TSQCalendarRowCell.h index 0fad589..e272a5e 100644 --- a/TimesSquare/TSQCalendarRowCell.h +++ b/TimesSquare/TSQCalendarRowCell.h @@ -15,6 +15,45 @@ */ @interface TSQCalendarRowCell : TSQCalendarCell +/** @name Text */ + +/** The font used to display each day of the month. + + This is the 19 point bold system font by default. + */ +@property (nonatomic, weak, readonly) UIFont *dayOfMonthFont; + + +@property (nonatomic, weak, readonly) UIFont *subtitleFont; + + +/** The text color for a day that's "today". + +This is white by default. +*/ +@property (nonatomic, weak, readonly) UIColor *todayTextColor; +@property (nonatomic, weak, readonly) UIColor *todayTextShadowColor; +@property (nonatomic, weak, readonly) UIColor *todaySubtitleTextColor; + +@property (nonatomic, weak, readonly) UIColor *textShadowColor; +@property (nonatomic, weak, readonly) UIColor *subtitleTextColor; + +/** The text color for a day that's selected + + This is white by default. + */ +@property (nonatomic, weak, readonly) UIColor *selectedTextColor; +@property (nonatomic, weak, readonly) UIColor *selectedTextShadowColor; +@property (nonatomic, weak, readonly) UIColor *selectedSubtitleTextColor; + +/** The text color for the initial day + + This uses the default text colors. + */ +@property (nonatomic, weak, readonly) UIColor *initialDayTextColor; +@property (nonatomic, weak, readonly) UIColor *initialDayTextShadowColor; +@property (nonatomic, weak, readonly) UIColor *initialDaySubtitleTextColor; + /** @name Images */ /** The background image for the entire row. @@ -37,12 +76,22 @@ */ @property (nonatomic, weak, readonly) UIImage *todayBackgroundImage; +/** The background image for the initial date. + + This is dark gray in the system's built-in Calendar app. You probably want to use a stretchable image. + */ +@property (nonatomic, weak, readonly) UIImage *initialDayBackgroundImage; + /** The background image for a day that's not this month. These are the trailing days from the previous month or the leading days from the following month. This can be `nil`. */ @property (nonatomic, weak, readonly) UIImage *notThisMonthBackgroundImage; +/** A small icon that appears below the day number to indicate today */ + +@property (nonatomic, weak, readonly) UIImage *todayIcon; + /** @name State Properties Set by Calendar View */ /** The date at the beginning of the week for this cell. @@ -57,6 +106,7 @@ */ @property (nonatomic, getter = isBottomRow) BOOL bottomRow; + /** Method to select a specific date within the week. This is funneled through and called by the calendar view, to facilitate deselection of other rows. @@ -65,4 +115,14 @@ */ - (void)selectColumnForDate:(NSDate *)date; +/** Method to select the initial date within the week. + + This is funneled through and called by the calendar view, to facilitate deselection of other rows. + + @param date The initial date to select, or nil to deselect all columns. + */ +- (void)selectColumnForInitialDate:(NSDate *)date; + +- (void)deselectColumnForDate:(NSDate *)date; + @end diff --git a/TimesSquare/TSQCalendarRowCell.m b/TimesSquare/TSQCalendarRowCell.m index 0cf087b..1d4538b 100644 --- a/TimesSquare/TSQCalendarRowCell.m +++ b/TimesSquare/TSQCalendarRowCell.m @@ -9,22 +9,19 @@ #import "TSQCalendarRowCell.h" #import "TSQCalendarView.h" - +#import "TSQCalendarDayButton.h" @interface TSQCalendarRowCell () @property (nonatomic, strong) NSArray *dayButtons; @property (nonatomic, strong) NSArray *notThisMonthButtons; -@property (nonatomic, strong) UIButton *todayButton; -@property (nonatomic, strong) UIButton *selectedButton; +@property (nonatomic, strong) TSQCalendarDayButton *selectedButton; -@property (nonatomic, assign) NSInteger indexOfTodayButton; @property (nonatomic, assign) NSInteger indexOfSelectedButton; @property (nonatomic, strong) NSDateFormatter *dayFormatter; @property (nonatomic, strong) NSDateFormatter *accessibilityFormatter; -@property (nonatomic, strong) NSDateComponents *todayDateComponents; @property (nonatomic) NSInteger monthOfBeginningDate; @end @@ -32,7 +29,7 @@ @interface TSQCalendarRowCell () @implementation TSQCalendarRowCell -- (id)initWithCalendar:(NSCalendar *)calendar reuseIdentifier:(NSString *)reuseIdentifier; +- (id)initWithCalendar:(NSCalendar *)calendar reuseIdentifier:(NSString *)reuseIdentifier { self = [super initWithCalendar:calendar reuseIdentifier:reuseIdentifier]; if (!self) { @@ -42,25 +39,98 @@ - (id)initWithCalendar:(NSCalendar *)calendar reuseIdentifier:(NSString *)reuseI return self; } -- (void)configureButton:(UIButton *)button; +#pragma mark - Fonts + +- (UIFont *)dayOfMonthFont +{ + return [UIFont boldSystemFontOfSize:19.0f]; +} + +- (UIFont *)subtitleFont +{ + return [UIFont boldSystemFontOfSize:12.0f]; +} + +#pragma mark - Colors + +- (UIColor *)todayTextColor +{ + return [UIColor whiteColor]; +} + +- (UIColor *)subtitleTextColor +{ + return [UIColor blackColor]; +} + +- (UIColor *)selectedTextColor +{ + return [UIColor whiteColor]; +} + +- (UIColor *)selectedSubtitleTextColor +{ + return [UIColor whiteColor]; +} + +- (UIColor *)textShadowColor +{ + return [UIColor whiteColor]; +} + +- (UIColor *)todayTextShadowColor +{ + return [UIColor colorWithWhite:0.0f alpha:0.75f]; +} + +- (UIColor *)selectedTextShadowColor +{ + return [UIColor colorWithWhite:0.0f alpha:0.75f]; +} + +- (UIColor *)todaySubtitleTextColor +{ + return [self subtitleTextColor]; +} + +- (UIColor *)initialDayTextColor { - button.titleLabel.font = [UIFont boldSystemFontOfSize:19.f]; + return [self textColor]; +} + +- (UIColor *)initialDayTextShadowColor +{ + return [self textShadowColor]; +} + +- (UIColor *)initialDaySubtitleTextColor +{ + return [self subtitleTextColor]; +} + +#pragma mark - Buttons + +- (void)configureButton:(TSQCalendarDayButton *)button +{ + [self updateAppearanceForButton:button]; + + button.titleLabel.font = [self dayOfMonthFont]; + button.tsqSubtitleLabel.font = [self subtitleFont]; + button.subtitleSymbolLabel.font = [self subtitleFont]; button.titleLabel.shadowOffset = self.shadowOffset; button.adjustsImageWhenDisabled = NO; - [button setTitleColor:self.textColor forState:UIControlStateNormal]; - [button setTitleShadowColor:[UIColor whiteColor] forState:UIControlStateNormal]; } - (void)createDayButtons; { NSMutableArray *dayButtons = [NSMutableArray arrayWithCapacity:self.daysInWeek]; for (NSUInteger index = 0; index < self.daysInWeek; index++) { - UIButton *button = [[UIButton alloc] initWithFrame:self.contentView.bounds]; - [button addTarget:self action:@selector(dateButtonPressed:) forControlEvents:UIControlEventTouchDown]; + TSQCalendarDayButton *button = [[TSQCalendarDayButton alloc] initWithFrame:self.contentView.bounds]; + button.type = CalendarButtonTypeNormalDay; + [button addTarget:self action:@selector(dateButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; [dayButtons addObject:button]; [self.contentView addSubview:button]; [self configureButton:button]; - [button setTitleColor:[self.textColor colorWithAlphaComponent:0.5f] forState:UIControlStateDisabled]; } self.dayButtons = dayButtons; } @@ -69,48 +139,352 @@ - (void)createNotThisMonthButtons; { NSMutableArray *notThisMonthButtons = [NSMutableArray arrayWithCapacity:self.daysInWeek]; for (NSUInteger index = 0; index < self.daysInWeek; index++) { - UIButton *button = [[UIButton alloc] initWithFrame:self.contentView.bounds]; + TSQCalendarDayButton *button = [[TSQCalendarDayButton alloc] initWithFrame:self.contentView.bounds]; + button.type = CalendarButtonTypeOtherMonth; [notThisMonthButtons addObject:button]; [self.contentView addSubview:button]; [self configureButton:button]; - button.enabled = NO; UIColor *backgroundPattern = [UIColor colorWithPatternImage:[self notThisMonthBackgroundImage]]; button.backgroundColor = backgroundPattern; - button.titleLabel.backgroundColor = backgroundPattern; } self.notThisMonthButtons = notThisMonthButtons; } -- (void)createTodayButton; +- (void)createSelectedButton; { - self.todayButton = [[UIButton alloc] initWithFrame:self.contentView.bounds]; - [self.contentView addSubview:self.todayButton]; - [self configureButton:self.todayButton]; - [self.todayButton addTarget:self action:@selector(todayButtonPressed:) forControlEvents:UIControlEventTouchDown]; - - [self.todayButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; - [self.todayButton setBackgroundImage:[self todayBackgroundImage] forState:UIControlStateNormal]; - [self.todayButton setTitleShadowColor:[UIColor colorWithWhite:0.0f alpha:0.75f] forState:UIControlStateNormal]; + TSQCalendarDayButton *button = [[TSQCalendarDayButton alloc] initWithFrame:self.contentView.bounds]; + button.type = CalendarButtonTypeSelected; + [button addTarget:self action:@selector(dateButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; + [self.contentView addSubview:button]; + [self configureButton:button]; + [button setBackgroundImage:[self selectedBackgroundImage] forState:UIControlStateNormal]; + [button setAccessibilityTraits:UIAccessibilityTraitSelected|button.accessibilityTraits]; + button.enabled = NO; + button.hidden = YES; + self.indexOfSelectedButton = -1; - self.todayButton.titleLabel.shadowOffset = CGSizeMake(0.0f, -1.0f / [UIScreen mainScreen].scale); + self.selectedButton = button; } -- (void)createSelectedButton; +- (void)updateAppearanceForButton:(TSQCalendarDayButton *)button +{ + UIColor *dateColor = nil; + UIColor *disabledDateColor = nil; + UIColor *dateShadowColor = nil; + + NSDate *date = button.day; + + BOOL dateIsSelectable = YES; + if ([self.calendarView.delegate respondsToSelector:@selector(calendarView:shouldSelectDate:)]) { + dateIsSelectable = [self.calendarView.delegate calendarView:self.calendarView shouldSelectDate:date]; + } + + // ** DISABLED DATE COLOR **/ + + if ([self.calendarView.delegate respondsToSelector:@selector(calendarView:disabledDateColorForDate:)]) { + // prefer the delegate disabledDate color over everything else; this will + // always be used if the delegate returns a color (except for other month + // buttons) + disabledDateColor = [self.calendarView.delegate calendarView:self.calendarView disabledDateColorForDate:date]; + } + + // if the delegate doesn't return a disabled date color, fall back to a sane + // default. Other month buttons will always get this disabled color. + if ((! disabledDateColor) || (button.type == CalendarButtonTypeOtherMonth)) + { + disabledDateColor = [self.textColor colorWithAlphaComponent:0.5f]; + } + + // ** DATE COLOR **/ + + // prefer the delegate date color over everything else; this will always be + // used if the delegate returns a color + UIColor *delegateDateColor = nil; + if ([self.calendarView.delegate respondsToSelector:@selector(calendarView:dateColorForDate:)]) { + delegateDateColor = [self.calendarView.delegate calendarView:self.calendarView dateColorForDate:date]; + } + + UIColor *delegateSelectedDateColor = nil; + if ([self.calendarView.delegate respondsToSelector:@selector(calendarView:selectedDateColorForDate:)]) { + delegateSelectedDateColor = [self.calendarView.delegate calendarView:self.calendarView selectedDateColorForDate:date]; + } + + // if the delegate doesn't return a date color, fall back to some sane defaults, + // which can still be overridden in subclasses + switch (button.type) + { + case CalendarButtonTypeNormalDay: + if (delegateDateColor) { + dateColor = delegateDateColor; + } else if ([button isForToday]) { + dateColor = [self todayTextColor]; + } else { + dateColor = self.textColor; + } + break; + + case CalendarButtonTypeOtherMonth: + dateColor = [self.textColor colorWithAlphaComponent:0.5f]; + break; + + case CalendarButtonTypeSelected: + if (delegateSelectedDateColor) { + dateColor = delegateSelectedDateColor; + } else { + dateColor = [self selectedTextColor]; + } + break; + + case CalendarButtonTypeInitialDay: + if (dateIsSelectable) { + if (delegateDateColor) { + dateColor = delegateDateColor; + } else if ([button isForToday]) { + dateColor = [self todayTextColor]; + } else { + dateColor = [self initialDayTextColor]; + } + } else { + dateColor = disabledDateColor; + } + } + + // ** DATE SHADOW COLOR **/ + + // prefer the delegate date shadow color over everything else; this will + // always be used if the delegate returns a color + UIColor *delegateDateShadowColor = nil; + if ([self.calendarView.delegate respondsToSelector:@selector(calendarView:dateShadowColorForDate:)]) { + delegateDateShadowColor = [self.calendarView.delegate calendarView:self.calendarView dateShadowColorForDate:date]; + } + + switch (button.type) + { + case CalendarButtonTypeNormalDay: + if (delegateDateShadowColor) { + dateShadowColor = delegateDateShadowColor; + } else if ([button isForToday]) { + dateShadowColor = [self todayTextShadowColor]; + } else { + dateShadowColor = [self textShadowColor]; + } + break; + + case CalendarButtonTypeOtherMonth: + break; + + case CalendarButtonTypeSelected: + dateShadowColor = [self selectedTextShadowColor]; + break; + + case CalendarButtonTypeInitialDay: + if (dateIsSelectable) { + if (delegateDateShadowColor) { + dateShadowColor = delegateDateShadowColor; + } else if ([button isForToday]) { + dateShadowColor = [self todayTextShadowColor]; + } else { + dateShadowColor = [self initialDayTextShadowColor]; + } + } + break; + } + + [button setTitleColor:dateColor forState:UIControlStateNormal]; + [button setTitleColor:disabledDateColor forState:UIControlStateDisabled]; + [button setTitleShadowColor:dateShadowColor forState:UIControlStateNormal]; + + // ** ICON **/ + + UIImage *icon = nil; + UIColor *iconTintColor = nil; + + if ([button isForToday] && button.type != CalendarButtonTypeOtherMonth) + { + icon = [self todayIcon]; + + if (button.type == CalendarButtonTypeSelected) + { + // when selected, tint the icon the same as selected text + icon = [icon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + iconTintColor = dateColor; + } + } + // can extend later to support other icons + button.iconImageView.image = icon; + button.iconImageView.tintColor = iconTintColor; +} + +- (void)updateBackgroundImageForButton:(TSQCalendarDayButton *)button isSelected:(BOOL)isSelected { - self.selectedButton = [[UIButton alloc] initWithFrame:self.contentView.bounds]; - [self.contentView addSubview:self.selectedButton]; - [self configureButton:self.selectedButton]; + NSDate *date = button.day; - [self.selectedButton setAccessibilityTraits:UIAccessibilityTraitSelected|self.selectedButton.accessibilityTraits]; + UIImage *delegateBackgroundImage = nil; + if ([self.calendarView.delegate respondsToSelector:@selector(calendarView:backgroundImageForDate:size:isInThisMonth:isSelected:)]) { + BOOL thisMonth = button.type != CalendarButtonTypeOtherMonth; + + delegateBackgroundImage = [self.calendarView.delegate calendarView:self.calendarView backgroundImageForDate:date size:button.bounds.size isInThisMonth:thisMonth isSelected:isSelected]; + } - self.selectedButton.enabled = NO; - [self.selectedButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; - [self.selectedButton setBackgroundImage:[self selectedBackgroundImage] forState:UIControlStateNormal]; - [self.selectedButton setTitleShadowColor:[UIColor colorWithWhite:0.0f alpha:0.75f] forState:UIControlStateNormal]; + + [button setBackgroundImage:delegateBackgroundImage forState:UIControlStateNormal]; +} + +- (void)updateTitleForButton:(TSQCalendarDayButton *)button +{ + NSDate *date = button.day; + + if (date == nil) { + return; + } + + [self updateBackgroundImageForButton:button isSelected:[self.calendarView.selectedDate isEqualToDate:button.day]]; + NSString *title = [self.dayFormatter stringFromDate:date]; + [button setTitle:title forState:UIControlStateNormal]; + [button setTitle:title forState:UIControlStateDisabled]; + + // add accessibility label + NSString *accessibilityLabel = [self.accessibilityFormatter stringFromDate:date]; + if (button.type == 1) { + [button setAccessibilityLabel:[NSString stringWithFormat:@"%@ Disabled", accessibilityLabel]]; + } else { + [button setAccessibilityLabel:accessibilityLabel]; + } + + // check if we should use an attributed string + NSDictionary *additionalAttributes = nil; + if ([self.calendarView.delegate respondsToSelector:@selector(calendarView:additionalDateTextAttributesForDate:)]) { + additionalAttributes = [self.calendarView.delegate calendarView:self.calendarView additionalDateTextAttributesForDate:date]; + } + + if (additionalAttributes) { + // create text attributes for normal button + NSMutableDictionary *normalAttributes = [additionalAttributes mutableCopy]; + + if ([button titleColorForState:UIControlStateNormal]) { + normalAttributes[NSForegroundColorAttributeName] = [button titleColorForState:UIControlStateNormal]; + } + + if ([button titleShadowColorForState:UIControlStateNormal]) { + NSShadow *shadow = [NSShadow new]; + shadow.shadowOffset = button.titleLabel.shadowOffset; + shadow.shadowColor = [button titleShadowColorForState:UIControlStateNormal]; + normalAttributes[NSShadowAttributeName] = shadow; + } + + // update button title with normal attributes + NSAttributedString *normalTitle = [[NSAttributedString alloc] initWithString:title attributes:normalAttributes]; + [button setAttributedTitle:normalTitle forState:UIControlStateNormal]; + + // create text attributes for disabled button + NSMutableDictionary *disabledAttributes = [normalAttributes mutableCopy]; + + if ([button titleColorForState:UIControlStateDisabled]) { + disabledAttributes[NSForegroundColorAttributeName] = [button titleColorForState:UIControlStateDisabled]; + } + + if ([button titleShadowColorForState:UIControlStateDisabled]) { + NSShadow *shadow = [NSShadow new]; + shadow.shadowOffset = button.titleLabel.shadowOffset; + shadow.shadowColor = [button titleShadowColorForState:UIControlStateDisabled]; + disabledAttributes[NSShadowAttributeName] = shadow; + } + + // update button title with normal attributes + NSAttributedString *disabledTitle = [[NSAttributedString alloc] initWithString:title attributes:disabledAttributes]; + [button setAttributedTitle:disabledTitle forState:UIControlStateDisabled]; + } +} + +- (void)updateSubtitlesForButton:(TSQCalendarDayButton *)button +{ + NSDate *date = button.day; + + NSString *subtitle = nil; + NSString *subtitleSymbol = nil; + UIColor *subtitleColor = nil; + + BOOL dateIsSelectable = YES; + if ([self.calendarView.delegate respondsToSelector:@selector(calendarView:shouldSelectDate:)]) { + dateIsSelectable = [self.calendarView.delegate calendarView:self.calendarView shouldSelectDate:date]; + } + + // ** DISABLED DATE COLOR **/ + UIColor *disabledDateColor = nil; + if ([self.calendarView.delegate respondsToSelector:@selector(calendarView:disabledDateColorForDate:)]) { + // prefer the delegate disabledDate color over everything else; this will + // always be used if the delegate returns a color (except for other month + // buttons) + disabledDateColor = [self.calendarView.delegate calendarView:self.calendarView disabledDateColorForDate:date]; + } + + // if the delegate doesn't return a disabled date color, fall back to a sane + // default. Other month buttons will always get this disabled color. + if ((! disabledDateColor) || (button.type == CalendarButtonTypeOtherMonth)) + { + disabledDateColor = [self.textColor colorWithAlphaComponent:0.5f]; + } - self.selectedButton.titleLabel.shadowOffset = CGSizeMake(0.0f, -1.0f / [UIScreen mainScreen].scale); - self.indexOfSelectedButton = -1; + if ([self.calendarView.delegate respondsToSelector:@selector(calendarView:subtitleForDate:)]) + { + subtitle = [self.calendarView.delegate calendarView:self.calendarView subtitleForDate:date]; + + // only check the color if the delegate also responds to the subtitle + // delegate method. Prefer this subtitle color returned by the delegate, + // except for other month buttons. + UIColor *delegateSubtitleColor = nil; + if ([self.calendarView.delegate respondsToSelector:@selector(calendarView:subtitleColorForDate:)]) { + delegateSubtitleColor = [self.calendarView.delegate calendarView:self.calendarView subtitleColorForDate:date]; + } + + switch (button.type) + { + case CalendarButtonTypeNormalDay: + if (delegateSubtitleColor) { + subtitleColor = delegateSubtitleColor; + } else if ([button isForToday]) { + subtitleColor = [self todaySubtitleTextColor]; + } else { + subtitleColor = [self subtitleTextColor]; + } + break; + + case CalendarButtonTypeOtherMonth: + // prefer a disabled color for other month buttons, even if the delegate + // returned a color + subtitleColor = disabledDateColor; + break; + + case CalendarButtonTypeSelected: + subtitleColor = [self selectedSubtitleTextColor]; + break; + + case CalendarButtonTypeInitialDay: + if (dateIsSelectable) { + if (delegateSubtitleColor) { + subtitleColor = delegateSubtitleColor; + } else if ([button isForToday]) { + subtitleColor = [self todaySubtitleTextColor]; + } else { + subtitleColor = [self initialDaySubtitleTextColor]; + } + } else { + subtitleColor = disabledDateColor; + } + break; + } + + if ([self.calendarView.delegate respondsToSelector:@selector(calendarView:subtitleTrailingSymbolForDate:)]) + { + subtitleSymbol = [self.calendarView.delegate calendarView:self.calendarView subtitleTrailingSymbolForDate:date]; + } + } + + button.tsqSubtitleLabel.text = subtitle; + button.subtitleSymbolLabel.text = subtitleSymbol; + button.tsqSubtitleLabel.textColor = subtitleColor; + button.subtitleSymbolLabel.textColor = subtitleColor; } - (void)setBeginningDate:(NSDate *)date; @@ -120,49 +494,54 @@ - (void)setBeginningDate:(NSDate *)date; if (!self.dayButtons) { [self createDayButtons]; [self createNotThisMonthButtons]; - [self createTodayButton]; [self createSelectedButton]; } NSDateComponents *offset = [NSDateComponents new]; offset.day = 1; - self.todayButton.hidden = YES; - self.indexOfTodayButton = -1; - self.selectedButton.hidden = YES; - self.indexOfSelectedButton = -1; - for (NSUInteger index = 0; index < self.daysInWeek; index++) { - NSString *title = [self.dayFormatter stringFromDate:date]; - NSString *accessibilityLabel = [self.accessibilityFormatter stringFromDate:date]; - [self.dayButtons[index] setTitle:title forState:UIControlStateNormal]; - [self.dayButtons[index] setAccessibilityLabel:accessibilityLabel]; - [self.notThisMonthButtons[index] setTitle:title forState:UIControlStateNormal]; - [self.notThisMonthButtons[index] setTitle:title forState:UIControlStateDisabled]; - [self.notThisMonthButtons[index] setAccessibilityLabel:accessibilityLabel]; - - NSDateComponents *thisDateComponents = [self.calendar components:NSDayCalendarUnit|NSMonthCalendarUnit|NSYearCalendarUnit fromDate:date]; + + TSQCalendarDayButton *currentDayButton = self.dayButtons[index]; + TSQCalendarDayButton *currentNotThisMonthButton = self.notThisMonthButtons[index]; + currentDayButton.day = date; + currentNotThisMonthButton.day = date; + + NSDateComponents *thisDateComponents = [self.calendar components:NSCalendarUnitDay|NSCalendarUnitMonth|NSCalendarUnitYear fromDate:date]; - [self.dayButtons[index] setHidden:YES]; - [self.notThisMonthButtons[index] setHidden:YES]; + [currentDayButton setHidden:YES]; + [currentNotThisMonthButton setHidden:YES]; NSInteger thisDayMonth = thisDateComponents.month; - if (self.monthOfBeginningDate != thisDayMonth) { - [self.notThisMonthButtons[index] setHidden:NO]; - } else { - - if ([self.todayDateComponents isEqual:thisDateComponents]) { - self.todayButton.hidden = NO; - [self.todayButton setTitle:title forState:UIControlStateNormal]; - [self.todayButton setAccessibilityLabel:accessibilityLabel]; - self.indexOfTodayButton = index; - } else { - UIButton *button = self.dayButtons[index]; - button.enabled = ![self.calendarView.delegate respondsToSelector:@selector(calendarView:shouldSelectDate:)] || [self.calendarView.delegate calendarView:self.calendarView shouldSelectDate:date]; - button.hidden = NO; + if (self.monthOfBeginningDate != thisDayMonth) + { + [currentNotThisMonthButton setHidden:NO]; + } + else + { + BOOL buttonEnabled = YES; + if ([self.calendarView.delegate respondsToSelector:@selector(calendarView:shouldSelectDate:)]) + { + buttonEnabled = [self.calendarView.delegate calendarView:self.calendarView shouldSelectDate:date]; } + + UIButton *button = self.dayButtons[index]; + button.enabled = buttonEnabled; + button.hidden = NO; } + // update button appearance + [self updateAppearanceForButton:currentDayButton]; + [self updateAppearanceForButton:currentNotThisMonthButton]; + + // update button title + [self updateTitleForButton:currentDayButton]; + [self updateTitleForButton:currentNotThisMonthButton]; + + // update button subtitles + [self updateSubtitlesForButton:currentDayButton]; + [self updateSubtitlesForButton:currentNotThisMonthButton]; + date = [self.calendar dateByAddingComponents:offset toDate:date options:0]; } } @@ -183,18 +562,10 @@ - (void)setBottomRow:(BOOL)bottomRow; - (IBAction)dateButtonPressed:(id)sender; { - NSDateComponents *offset = [NSDateComponents new]; - offset.day = [self.dayButtons indexOfObject:sender]; - NSDate *selectedDate = [self.calendar dateByAddingComponents:offset toDate:self.beginningDate options:0]; - self.calendarView.selectedDate = selectedDate; -} - -- (IBAction)todayButtonPressed:(id)sender; -{ - NSDateComponents *offset = [NSDateComponents new]; - offset.day = self.indexOfTodayButton; - NSDate *selectedDate = [self.calendar dateByAddingComponents:offset toDate:self.beginningDate options:0]; + TSQCalendarDayButton *dayButton = (TSQCalendarDayButton *)sender; + NSDate *selectedDate = dayButton.day; self.calendarView.selectedDate = selectedDate; + [self updateBackgroundImageForButton:dayButton isSelected:YES]; } - (void)layoutSubviews; @@ -205,26 +576,60 @@ - (void)layoutSubviews; [super layoutSubviews]; - self.backgroundView.frame = self.bounds; + // Size the background view with horizontal insets + CGRect bounds = self.bounds; + UIEdgeInsets insets = self.calendarView.contentInset; + CGRect insetRect = UIEdgeInsetsInsetRect(bounds, insets); + insetRect.origin.y = bounds.origin.y; + insetRect.size.height = bounds.size.height; + self.backgroundView.frame = insetRect; } - (void)layoutViewsForColumnAtIndex:(NSUInteger)index inRect:(CGRect)rect; { - UIButton *dayButton = self.dayButtons[index]; - UIButton *notThisMonthButton = self.notThisMonthButtons[index]; - - dayButton.frame = rect; - notThisMonthButton.frame = rect; - - if (self.indexOfTodayButton == (NSInteger)index) { - self.todayButton.frame = rect; + // find buttons that we need to update the frame + NSMutableArray *buttons = [NSMutableArray new]; + if (index < self.dayButtons.count) { + [buttons addObject:self.dayButtons[index]]; } - if (self.indexOfSelectedButton == (NSInteger)index) { - self.selectedButton.frame = rect; + if (index < self.notThisMonthButtons.count) { + [buttons addObject:self.notThisMonthButtons[index]]; + } + if (self.indexOfSelectedButton == (NSInteger)index && self.selectedButton) { + [buttons addObject:self.selectedButton]; + } + + for (TSQCalendarDayButton *button in buttons) { + if (CGRectEqualToRect(button.frame, rect) == NO) { + button.frame = rect; + // image views are dependant on button size so they need to be regenerated + [self updateBackgroundImageForButton:button isSelected:[self.calendarView.selectedDate isEqualToDate:button.day]]; + } } } - (void)selectColumnForDate:(NSDate *)date; +{ + [self selectColumnForDate:date isInitialDay:NO]; +} + +- (void)deselectColumnForDate:(NSDate *)date +{ + for (TSQCalendarDayButton *button in self.dayButtons) { + if ([button.day isEqualToDate:date]) + { + [self updateBackgroundImageForButton:button isSelected:NO]; + break; + } + } +} + +- (void)selectColumnForInitialDate:(NSDate *)date +{ + [self selectColumnForDate:date isInitialDay:YES]; +} + +- (void)selectColumnForDate:(NSDate *)date isInitialDay:(BOOL)isInitialDay { if (!date && self.indexOfSelectedButton == -1) { return; @@ -232,9 +637,9 @@ - (void)selectColumnForDate:(NSDate *)date; NSInteger newIndexOfSelectedButton = -1; if (date) { - NSInteger thisDayMonth = [self.calendar components:NSMonthCalendarUnit fromDate:date].month; + NSInteger thisDayMonth = [self.calendar components:NSCalendarUnitMonth fromDate:date].month; if (self.monthOfBeginningDate == thisDayMonth) { - newIndexOfSelectedButton = [self.calendar components:NSDayCalendarUnit fromDate:self.beginningDate toDate:date options:0].day; + newIndexOfSelectedButton = [self.calendar components:NSCalendarUnitDay fromDate:self.beginningDate toDate:date options:0].day; if (newIndexOfSelectedButton >= (NSInteger)self.daysInWeek) { newIndexOfSelectedButton = -1; } @@ -244,15 +649,29 @@ - (void)selectColumnForDate:(NSDate *)date; self.indexOfSelectedButton = newIndexOfSelectedButton; if (newIndexOfSelectedButton >= 0) { + // update selected button colors + TSQCalendarDayButton *dayButton = self.dayButtons[newIndexOfSelectedButton]; + self.selectedButton.day = dayButton.day; + self.selectedButton.type = isInitialDay ? CalendarButtonTypeInitialDay : CalendarButtonTypeSelected; + self.selectedButton.enabled = isInitialDay; + self.selectedButton.isInitialDay = isInitialDay; + [self updateAppearanceForButton:self.selectedButton]; + [self updateSubtitlesForButton:self.selectedButton]; + + // update background image + [self updateBackgroundImageForButton:self.selectedButton isSelected:NO]; + + // update selected button text self.selectedButton.hidden = NO; - NSString *newTitle = [self.dayButtons[newIndexOfSelectedButton] currentTitle]; - [self.selectedButton setTitle:newTitle forState:UIControlStateNormal]; - [self.selectedButton setTitle:newTitle forState:UIControlStateDisabled]; - [self.selectedButton setAccessibilityLabel:[self.dayButtons[newIndexOfSelectedButton] accessibilityLabel]]; + self.selectedButton.enabled = YES; + [self updateTitleForButton:self.selectedButton]; + self.selectedButton.tsqSubtitleLabel.text = dayButton.tsqSubtitleLabel.text; + self.selectedButton.subtitleSymbolLabel.text = dayButton.subtitleSymbolLabel.text; } else { self.selectedButton.hidden = YES; + self.selectedButton.enabled = NO; } - + [self setNeedsLayout]; } @@ -279,7 +698,7 @@ - (NSDateFormatter *)accessibilityFormatter; - (NSInteger)monthOfBeginningDate; { if (!_monthOfBeginningDate) { - _monthOfBeginningDate = [self.calendar components:NSMonthCalendarUnit fromDate:self.firstOfMonth].month; + _monthOfBeginningDate = [self.calendar components:NSCalendarUnitMonth fromDate:self.firstOfMonth].month; } return _monthOfBeginningDate; } @@ -290,12 +709,4 @@ - (void)setFirstOfMonth:(NSDate *)firstOfMonth; self.monthOfBeginningDate = 0; } -- (NSDateComponents *)todayDateComponents; -{ - if (!_todayDateComponents) { - self.todayDateComponents = [self.calendar components:NSDayCalendarUnit|NSMonthCalendarUnit|NSYearCalendarUnit fromDate:[NSDate date]]; - } - return _todayDateComponents; -} - @end diff --git a/TimesSquare/TSQCalendarView.h b/TimesSquare/TSQCalendarView.h index 3a1c025..2dd54ff 100644 --- a/TimesSquare/TSQCalendarView.h +++ b/TimesSquare/TSQCalendarView.h @@ -42,6 +42,15 @@ */ @property (nonatomic, strong) NSDate *selectedDate; + +/** The initial date to be highlighted on the calendar. + + Set this property to any `NSDate`; `TSQCalendarView` will only look at the month, day, and year. + You can read and write this property + */ + +@property (nonatomic, strong) NSDate *initialDate; + /** @name Calendar Configuration */ /** The calendar type to use when displaying. @@ -83,6 +92,12 @@ */ @property (nonatomic) CGPoint contentOffset; +/** The size of the calendar content view + + This property is equivalent to the one defined on `UIScrollView`. + */ +@property (nonatomic) CGSize contentSize; + /** The cell class to use for month headers. Since there's very little configuration to be done for each cell, this can be set as a shortcut to implementing a data source. @@ -97,6 +112,14 @@ */ @property (nonatomic, strong) Class rowCellClass; +// Returns and modifies the scrollEnabled on the table view + +@property (nonatomic) BOOL scrollEnabled; + +// Returns and modifies the showsVerticalScrollIndicator on the table view + +@property (nonatomic) BOOL showsVerticalScrollIndicator; + /** Scrolls the receiver until the specified date month is completely visible. @param date A date that identifies the month that will be visible. @@ -134,6 +157,24 @@ */ - (BOOL)calendarView:(TSQCalendarView *)calendarView shouldSelectDate:(NSDate *)date; +- (NSString*)calendarView: (TSQCalendarView *)calendarView subtitleForDate: (NSDate*) date; + +- (NSString*)calendarView: (TSQCalendarView *)calendarView subtitleTrailingSymbolForDate: (NSDate*) date; + +- (UIColor*)calendarView: (TSQCalendarView *)calendarView dateColorForDate: (NSDate*) date; + +- (UIColor*)calendarView: (TSQCalendarView *)calendarView dateShadowColorForDate: (NSDate*) date; + +- (UIColor*)calendarView: (TSQCalendarView *)calendarView disabledDateColorForDate: (NSDate*) date; + +- (UIColor*)calendarView: (TSQCalendarView *)calendarView subtitleColorForDate: (NSDate*) date; + +- (UIColor*)calendarView: (TSQCalendarView *)calendarView selectedDateColorForDate: (NSDate*) date; + +- (UIImage*)calendarView: (TSQCalendarView *)calendarView backgroundImageForDate: (NSDate*) date size:(CGSize)size isInThisMonth:(BOOL)thisMonth isSelected:(BOOL)isSelected; + +- (NSDictionary*)calendarView: (TSQCalendarView *)calendarView additionalDateTextAttributesForDate: (NSDate*) date; + /** Tells the delegate that a particular date was selected. @param calendarView The calendar view that is selecting a date. diff --git a/TimesSquare/TSQCalendarView.m b/TimesSquare/TSQCalendarView.m index 7bd565f..5c499f5 100644 --- a/TimesSquare/TSQCalendarView.m +++ b/TimesSquare/TSQCalendarView.m @@ -52,7 +52,12 @@ - (void)_TSQCalendarView_commonInit; _tableView.delegate = self; _tableView.separatorStyle = UITableViewCellSeparatorStyleNone; _tableView.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth; - [self addSubview:_tableView]; + + // WORKAROUND: In iOS8, scrolling randomly doesn't work in the calendar view when dragging on enabled buttons. + // The correct solution is to enable delaysContentTouches but this has been broken since iOS7: ( https://devforums.apple.com/thread/199755?start=0&tstart=0 ). + // The workaround here is to set delaysTouchesBegan on the panGestureRecognizer of the table itself. + _tableView.panGestureRecognizer.delaysTouchesBegan = YES; + [self addSubview:_tableView]; } - (void)dealloc; @@ -85,15 +90,6 @@ - (Class)rowCellClass; return _rowCellClass; } -- (Class)cellClassForRowAtIndexPath:(NSIndexPath *)indexPath; -{ - if (indexPath.row == 0 && !self.pinsHeaderToTop) { - return [self headerCellClass]; - } else { - return [self rowCellClass]; - } -} - - (void)setBackgroundColor:(UIColor *)backgroundColor; { [super setBackgroundColor:backgroundColor]; @@ -109,13 +105,13 @@ - (void)setPinsHeaderToTop:(BOOL)pinsHeaderToTop; - (void)setFirstDate:(NSDate *)firstDate; { // clamp to the beginning of its month - _firstDate = [self clampDate:firstDate toComponents:NSMonthCalendarUnit|NSYearCalendarUnit]; + _firstDate = [self clampDate:firstDate toComponents:NSCalendarUnitMonth|NSCalendarUnitYear]; } - (void)setLastDate:(NSDate *)lastDate; { // clamp to the end of its month - NSDate *firstOfMonth = [self clampDate:lastDate toComponents:NSMonthCalendarUnit|NSYearCalendarUnit]; + NSDate *firstOfMonth = [self clampDate:lastDate toComponents:NSCalendarUnitMonth|NSCalendarUnitYear]; NSDateComponents *offsetComponents = [[NSDateComponents alloc] init]; offsetComponents.month = 1; @@ -126,15 +122,62 @@ - (void)setLastDate:(NSDate *)lastDate; - (void)setSelectedDate:(NSDate *)newSelectedDate; { // clamp to beginning of its day - NSDate *startOfDay = [self clampDate:newSelectedDate toComponents:NSDayCalendarUnit|NSMonthCalendarUnit|NSYearCalendarUnit]; + NSDate *startOfDay = [self clampDate:newSelectedDate toComponents:NSCalendarUnitDay|NSCalendarUnitMonth|NSCalendarUnitYear]; if ([self.delegate respondsToSelector:@selector(calendarView:shouldSelectDate:)] && ![self.delegate calendarView:self shouldSelectDate:startOfDay]) { return; } + [self updateSelectedDate:startOfDay isInitialDate:NO]; + + _selectedDate = startOfDay; + + if (startOfDay == nil && self.initialDate != nil) { + [self updateSelectedDate:self.initialDate isInitialDate:YES]; + } + + if ([self.delegate respondsToSelector:@selector(calendarView:didSelectDate:)]) { + [self.delegate calendarView:self didSelectDate:startOfDay]; + } +} + +- (void)setInitialDate:(NSDate *)initialDate +{ + // clamp to beginning of its day + NSDate *startOfDay = [self clampDate:initialDate toComponents:NSCalendarUnitDay|NSCalendarUnitMonth|NSCalendarUnitYear]; + + if (self.selectedDate == nil) { + // only show initial date if user hasn't already selected a date + [self updateSelectedDate:startOfDay isInitialDate:YES]; + } + + _initialDate = startOfDay; +} + +- (void)updateSelectedDate:(NSDate *)date isInitialDate:(BOOL)isInitialDate +{ + [[self cellForRowAtDate:_selectedDate] deselectColumnForDate:self.selectedDate]; + // clear existing selected cells [[self cellForRowAtDate:_selectedDate] selectColumnForDate:nil]; - [[self cellForRowAtDate:startOfDay] selectColumnForDate:startOfDay]; - NSIndexPath *newIndexPath = [self indexPathForRowAtDate:startOfDay]; + [[self cellForRowAtDate:_initialDate] selectColumnForDate:nil]; + + // update new selected cell + TSQCalendarRowCell *dateRowCell = [self cellForRowAtDate:date]; + if (dateRowCell == nil) { + return; + } + + if (isInitialDate) { + [dateRowCell selectColumnForInitialDate:date]; + } else { + [dateRowCell selectColumnForDate:date]; + } + + NSIndexPath *newIndexPath = [self indexPathForRowAtDate:date]; + if (newIndexPath == nil) { + return; + } + CGRect newIndexPathRect = [self.tableView rectForRowAtIndexPath:newIndexPath]; CGRect scrollBounds = self.tableView.bounds; @@ -148,12 +191,16 @@ - (void)setSelectedDate:(NSDate *)newSelectedDate; [self.tableView scrollToRowAtIndexPath:newIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES]; } } - - _selectedDate = startOfDay; - - if ([self.delegate respondsToSelector:@selector(calendarView:didSelectDate:)]) { - [self.delegate calendarView:self didSelectDate:startOfDay]; - } +} + +- (BOOL)scrollEnabled +{ + return self.tableView.scrollEnabled; +} + +- (void)setScrollEnabled:(BOOL)scrollEnabled +{ + self.tableView.scrollEnabled = scrollEnabled; } - (void)scrollToDate:(NSDate *)date animated:(BOOL)animated @@ -182,7 +229,17 @@ - (TSQCalendarMonthHeaderCell *)makeHeaderCellWithIdentifier:(NSString *)identif return cell; } -#pragma mark Calendar calculations +- (void)setShowsVerticalScrollIndicator:(BOOL)showsVerticalScrollIndicator +{ + self.tableView.showsVerticalScrollIndicator = showsVerticalScrollIndicator; +} + +- (BOOL)showsVerticalScrollIndicator +{ + return self.tableView.showsVerticalScrollIndicator; +} + +#pragma mark - Calendar calculations - (NSDate *)firstOfMonthForSection:(NSInteger)section; { @@ -198,7 +255,7 @@ - (TSQCalendarRowCell *)cellForRowAtDate:(NSDate *)date; - (NSInteger)sectionForDate:(NSDate *)date; { - return [self.calendar components:NSMonthCalendarUnit fromDate:self.firstDate toDate:date options:0].month; + return [self.calendar components:NSCalendarUnitMonth fromDate:self.firstDate toDate:date options:0].month; } - (NSIndexPath *)indexPathForRowAtDate:(NSDate *)date; @@ -210,13 +267,13 @@ - (NSIndexPath *)indexPathForRowAtDate:(NSDate *)date; NSInteger section = [self sectionForDate:date]; NSDate *firstOfMonth = [self firstOfMonthForSection:section]; - NSInteger firstWeek = [self.calendar components:NSWeekOfMonthCalendarUnit fromDate:firstOfMonth].weekOfMonth; - NSInteger targetWeek = [self.calendar components:NSWeekOfMonthCalendarUnit fromDate:date].weekOfMonth; + NSInteger firstWeek = [self.calendar components:NSCalendarUnitWeekOfMonth fromDate:firstOfMonth].weekOfMonth; + NSInteger targetWeek = [self.calendar components:NSCalendarUnitWeekOfMonth fromDate:date].weekOfMonth; - return [NSIndexPath indexPathForRow:(self.pinsHeaderToTop ? 0 : 1) + targetWeek - firstWeek inSection:section]; + return [NSIndexPath indexPathForRow:targetWeek - firstWeek inSection:section]; } -#pragma mark UIView +#pragma mark - UIView - (void)layoutSubviews; { @@ -245,67 +302,87 @@ - (void)layoutSubviews; } } -#pragma mark UITableViewDataSource +#pragma mark - UITableViewDataSource - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView; { - return 1 + [self.calendar components:NSMonthCalendarUnit fromDate:self.firstDate toDate:self.lastDate options:0].month; + return 1 + [self.calendar components:NSCalendarUnitMonth fromDate:self.firstDate toDate:self.lastDate options:0].month; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section; { NSDate *firstOfMonth = [self firstOfMonthForSection:section]; - NSRange rangeOfWeeks = [self.calendar rangeOfUnit:NSWeekCalendarUnit inUnit:NSMonthCalendarUnit forDate:firstOfMonth]; - return (self.pinsHeaderToTop ? 0 : 1) + rangeOfWeeks.length; + + // using the new calendar units, even though they are available in iOS 7.0, don't actually work for this calculation + // on iOS 7. So we can't use the new units here. + NSRange rangeOfWeeks = [self.calendar rangeOfUnit:NSCalendarUnitWeekOfMonth inUnit:NSCalendarUnitMonth forDate:firstOfMonth]; + return rangeOfWeeks.length; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath; { - if (indexPath.row == 0 && !self.pinsHeaderToTop) { - // month header - static NSString *identifier = @"header"; - TSQCalendarMonthHeaderCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; - if (!cell) { - cell = [self makeHeaderCellWithIdentifier:identifier]; - } - return cell; - } else { - static NSString *identifier = @"row"; - TSQCalendarRowCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; - if (!cell) { - cell = [[[self rowCellClass] alloc] initWithCalendar:self.calendar reuseIdentifier:identifier]; - cell.backgroundColor = self.backgroundColor; - cell.calendarView = self; - } - return cell; + static NSString *identifier = @"row"; + TSQCalendarRowCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; + if (!cell) { + cell = [[[self rowCellClass] alloc] initWithCalendar:self.calendar reuseIdentifier:identifier]; + cell.backgroundColor = self.backgroundColor; + cell.calendarView = self; } + return cell; } -#pragma mark UITableViewDelegate +#pragma mark - UITableViewDelegate - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath; { NSDate *firstOfMonth = [self firstOfMonthForSection:indexPath.section]; [(TSQCalendarCell *)cell setFirstOfMonth:firstOfMonth]; - if (indexPath.row > 0 || self.pinsHeaderToTop) { - NSInteger ordinalityOfFirstDay = [self.calendar ordinalityOfUnit:NSDayCalendarUnit inUnit:NSWeekCalendarUnit forDate:firstOfMonth]; - NSDateComponents *dateComponents = [NSDateComponents new]; - dateComponents.day = 1 - ordinalityOfFirstDay; - dateComponents.week = indexPath.row - (self.pinsHeaderToTop ? 0 : 1); - [(TSQCalendarRowCell *)cell setBeginningDate:[self.calendar dateByAddingComponents:dateComponents toDate:firstOfMonth options:0]]; + NSInteger ordinalityOfFirstDay = [self.calendar ordinalityOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitWeekOfMonth forDate:firstOfMonth]; + NSDateComponents *dateComponents = [NSDateComponents new]; + dateComponents.day = 1 - ordinalityOfFirstDay; + dateComponents.weekOfMonth = indexPath.row; + [(TSQCalendarRowCell *)cell setBeginningDate:[self.calendar dateByAddingComponents:dateComponents toDate:firstOfMonth options:0]]; + + if (self.selectedDate) { [(TSQCalendarRowCell *)cell selectColumnForDate:self.selectedDate]; - - BOOL isBottomRow = (indexPath.row == [self tableView:tableView numberOfRowsInSection:indexPath.section] - (self.pinsHeaderToTop ? 0 : 1)); - [(TSQCalendarRowCell *)cell setBottomRow:isBottomRow]; + } else if (self.initialDate) { + [(TSQCalendarRowCell *)cell selectColumnForInitialDate:self.initialDate]; } + + BOOL isBottomRow = indexPath.row == [self tableView:tableView numberOfRowsInSection:indexPath.section] - 1; + [(TSQCalendarRowCell *)cell setBottomRow:isBottomRow]; } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath; { - return [[self cellClassForRowAtIndexPath:indexPath] cellHeight]; + return [[self rowCellClass] cellHeight]; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section +{ + if (self.pinsHeaderToTop) { + return 0; + } + return [[self headerCellClass] cellHeight]; +} + +- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section +{ + if (self.pinsHeaderToTop) { + return nil; + } + + static NSString *identifier = @"header"; + TSQCalendarMonthHeaderCell *cell = (TSQCalendarMonthHeaderCell *)[tableView dequeueReusableHeaderFooterViewWithIdentifier:identifier]; + if (!cell) { + cell = [self makeHeaderCellWithIdentifier:identifier]; + cell.firstOfMonth = [self firstOfMonthForSection:section]; + } + return cell; } -#pragma mark UIScrollViewDelegate + +#pragma mark - UIScrollViewDelegate - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset; { @@ -331,8 +408,18 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView; - (NSDate *)clampDate:(NSDate *)date toComponents:(NSUInteger)unitFlags { + if (date == nil) { + // [__NSCFCalendar components:fromDate:]: date cannot be nil + return nil; + } + NSDateComponents *components = [self.calendar components:unitFlags fromDate:date]; return [self.calendar dateFromComponents:components]; } +- (CGSize)contentSize +{ + return self.tableView.contentSize; +} + @end