diff --git a/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls.xcodeproj/project.pbxproj b/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls.xcodeproj/project.pbxproj new file mode 100644 index 00000000..33e84872 --- /dev/null +++ b/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls.xcodeproj/project.pbxproj @@ -0,0 +1,345 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + F83BD17421E5426C006148A4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F83BD17321E5426C006148A4 /* AppDelegate.swift */; }; + F83BD17621E5426C006148A4 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F83BD17521E5426C006148A4 /* ViewController.swift */; }; + F83BD17921E5426C006148A4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F83BD17721E5426C006148A4 /* Main.storyboard */; }; + F83BD17B21E5426D006148A4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F83BD17A21E5426D006148A4 /* Assets.xcassets */; }; + F83BD17E21E5426D006148A4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F83BD17C21E5426D006148A4 /* LaunchScreen.storyboard */; }; + F83BD18621E54645006148A4 /* CustomControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = F83BD18521E54645006148A4 /* CustomControl.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + F83BD17021E5426C006148A4 /* Custom Controls.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Custom Controls.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + F83BD17321E5426C006148A4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + F83BD17521E5426C006148A4 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + F83BD17821E5426C006148A4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + F83BD17A21E5426D006148A4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + F83BD17D21E5426D006148A4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + F83BD17F21E5426D006148A4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + F83BD18521E54645006148A4 /* CustomControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomControl.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + F83BD16D21E5426C006148A4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + F83BD16721E5426C006148A4 = { + isa = PBXGroup; + children = ( + F83BD17221E5426C006148A4 /* Custom Controls */, + F83BD17121E5426C006148A4 /* Products */, + ); + sourceTree = ""; + }; + F83BD17121E5426C006148A4 /* Products */ = { + isa = PBXGroup; + children = ( + F83BD17021E5426C006148A4 /* Custom Controls.app */, + ); + name = Products; + sourceTree = ""; + }; + F83BD17221E5426C006148A4 /* Custom Controls */ = { + isa = PBXGroup; + children = ( + F83BD17321E5426C006148A4 /* AppDelegate.swift */, + F83BD17521E5426C006148A4 /* ViewController.swift */, + F83BD17721E5426C006148A4 /* Main.storyboard */, + F83BD18521E54645006148A4 /* CustomControl.swift */, + F83BD17A21E5426D006148A4 /* Assets.xcassets */, + F83BD17C21E5426D006148A4 /* LaunchScreen.storyboard */, + F83BD17F21E5426D006148A4 /* Info.plist */, + ); + path = "Custom Controls"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + F83BD16F21E5426C006148A4 /* Custom Controls */ = { + isa = PBXNativeTarget; + buildConfigurationList = F83BD18221E5426D006148A4 /* Build configuration list for PBXNativeTarget "Custom Controls" */; + buildPhases = ( + F83BD16C21E5426C006148A4 /* Sources */, + F83BD16D21E5426C006148A4 /* Frameworks */, + F83BD16E21E5426C006148A4 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Custom Controls"; + productName = "Custom Controls"; + productReference = F83BD17021E5426C006148A4 /* Custom Controls.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + F83BD16821E5426C006148A4 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1010; + LastUpgradeCheck = 1010; + ORGANIZATIONNAME = "Jaspal Suri"; + TargetAttributes = { + F83BD16F21E5426C006148A4 = { + CreatedOnToolsVersion = 10.1; + }; + }; + }; + buildConfigurationList = F83BD16B21E5426C006148A4 /* Build configuration list for PBXProject "Custom Controls" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = F83BD16721E5426C006148A4; + productRefGroup = F83BD17121E5426C006148A4 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + F83BD16F21E5426C006148A4 /* Custom Controls */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + F83BD16E21E5426C006148A4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F83BD17E21E5426D006148A4 /* LaunchScreen.storyboard in Resources */, + F83BD17B21E5426D006148A4 /* Assets.xcassets in Resources */, + F83BD17921E5426C006148A4 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + F83BD16C21E5426C006148A4 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F83BD17621E5426C006148A4 /* ViewController.swift in Sources */, + F83BD18621E54645006148A4 /* CustomControl.swift in Sources */, + F83BD17421E5426C006148A4 /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + F83BD17721E5426C006148A4 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + F83BD17821E5426C006148A4 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + F83BD17C21E5426D006148A4 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + F83BD17D21E5426D006148A4 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + F83BD18021E5426D006148A4 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + F83BD18121E5426D006148A4 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + F83BD18321E5426D006148A4 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 6Y6994DAGM; + INFOPLIST_FILE = "Custom Controls/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.JaspalSuri.Custom-Controls"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.2; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + F83BD18421E5426D006148A4 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 6Y6994DAGM; + INFOPLIST_FILE = "Custom Controls/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.JaspalSuri.Custom-Controls"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.2; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + F83BD16B21E5426C006148A4 /* Build configuration list for PBXProject "Custom Controls" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F83BD18021E5426D006148A4 /* Debug */, + F83BD18121E5426D006148A4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F83BD18221E5426D006148A4 /* Build configuration list for PBXNativeTarget "Custom Controls" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F83BD18321E5426D006148A4 /* Debug */, + F83BD18421E5426D006148A4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = F83BD16821E5426C006148A4 /* Project object */; +} diff --git a/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/AppDelegate.swift b/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/AppDelegate.swift new file mode 100644 index 00000000..41122fa2 --- /dev/null +++ b/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/AppDelegate.swift @@ -0,0 +1,12 @@ +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + return true + } + +} diff --git a/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/Assets.xcassets/AppIcon.appiconset/Contents.json b/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d8db8d65 --- /dev/null +++ b/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/Assets.xcassets/Contents.json b/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/Assets.xcassets/Contents.json new file mode 100644 index 00000000..da4a164c --- /dev/null +++ b/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/Base.lproj/LaunchScreen.storyboard b/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..bfa36129 --- /dev/null +++ b/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/Base.lproj/Main.storyboard b/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/Base.lproj/Main.storyboard new file mode 100644 index 00000000..a890b531 --- /dev/null +++ b/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/Base.lproj/Main.storyboard @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/CustomControl.swift b/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/CustomControl.swift new file mode 100644 index 00000000..cc631247 --- /dev/null +++ b/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/CustomControl.swift @@ -0,0 +1,102 @@ +import UIKit + +class CustomControl: UIControl { + var value: Int = 1 + + // private constants + + private let componentDimension: CGFloat = 40 + private let componentCount = 5 + private let componentActiveColor = UIColor.black + private let componentInactiveColor = UIColor.gray + + required init?(coder aCoder: NSCoder) { + super.init().CustomControl.setup() + } + + var labelArray: [UILabel] = [] + + func setup() { + for index in 0.. Bool { + // Track the touch location in the view + let touchPoint = touch.location(in: self) + // Set the color based on the user touch location + sendActions(for: [.touchDown, .valueChanged]) + return true + } + + override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + print("Continue tracking touch: \(touch.location(in: self))") + let touchPoint = touch.location(in: self) + if bounds.contains(touchPoint) { + sendActions(for: [.touchDragInside, .valueChanged]) + } else { + sendActions(for: [.touchDragOutside]) + } + // Continues tracking + return true + } + + + override func endTracking(_ touch: UITouch?, with event: UIEvent?) { + // Make sure to call this (super.endTracking) no matter what happens, at the end of the execution of the function. + defer { + super.endTracking(touch, with: event) + } + + guard let touch = touch else { + return + } + + let touchPoint = touch.location(in: self) + if bounds.contains(touchPoint) { + sendActions(for: [.touchUpInside, .valueChanged]) + } else { + sendActions(for: [.touchUpOutside]) + } + } + + override func cancelTracking(with event: UIEvent?) { + sendActions(for: [.touchCancel]) + super.cancelTracking(with: event) + } + + override func updateValue(at touch: UITouch) { + for.loop.componentLabels { + guard let touchLocation.labelFrame == true + else { return } + } + touch.meets.label = controlValue.labelArray(tag.indexRow.path) + sendAction(for: [valueChanged]) + } + var color: UIColor = .white +} diff --git a/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/Info.plist b/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/Info.plist new file mode 100644 index 00000000..16be3b68 --- /dev/null +++ b/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/ViewController.swift b/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/ViewController.swift new file mode 100644 index 00000000..0b19651e --- /dev/null +++ b/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/ViewController.swift @@ -0,0 +1,14 @@ +import UIKit + +class ViewController: UIViewController { + + @IBAction func updateRating(_ sender: CustomControl) { + static var starCount = 0 + if starCount == 1 { + self.navigationItem.title = "User Rating: 1 Star" + } else { + self.navigationItem.title = "User Rating: \(starCount) Stars" + } + } +} + diff --git a/Sprint 6/Module 2-Custom Controls/README-Progress.md b/Sprint 6/Module 2-Custom Controls/README-Progress.md new file mode 100644 index 00000000..118dcc16 --- /dev/null +++ b/Sprint 6/Module 2-Custom Controls/README-Progress.md @@ -0,0 +1,46 @@ +## Respond to touches. + +Follow these steps to finish up your control and add user feedback. + +1. In `updateValue`, you handle touches by checking to see whether they intersect with any of your stored label subviews. Implement a loop that iterates through your component labels and detect whether each touch's location (`touch.location(in: self)`) is contained in each label's frame. +2. When a touch overlaps a label, set the control's value to that tag, update the label colors to reflect the current touch, and send an action for `valueChanged`. +3. **Stretch** It's better to store the old value before changing it and only send an update when the value has changed. +4. **Stretch** Add the following `UIView` animation to flare the view a little when selected. + +``` +extension UIView { +// "Flare view" animation sequence +func performFlare() { +func flare() { transform = CGAffineTransform(scaleX: 1.6, y: 1.6) } +func unflare() { transform = .identity } + +UIView.animate(withDuration: 0.3, +animations: { flare() }, +completion: { _ in UIView.animate(withDuration: 0.1) { unflare() }}) +} +} +``` + +This method adjusts your view's `transform`, telling it to inflate to 60% larger than its normal size and then shrink back down to its default size (using the "no change" `identity` transform). + +## Test +1. Run the project and make sure everything works. +2. If anything doesn't work the way the video shows (outside of the two stretch goals), go back and debug your issues. +3. As always, if you need help, follow the 20-minute rule, then ask your PM. + +## Go Farther +Time allowing, here are some things you can try. + +1. Change the number of buttons to allow 4 or 6 star rating controls. Does everything work right with your change being in a single line of code? +2. Flip the control for use in right-to-left language countries, where you slide from right to left. +3. Create different flare animations. Things you can animate include background color, translucency (the view's `alpha` property), and rotation. You'll be able to find examples of these with simple web searches. + +## References + +Here are some helpful resources for your project: + +* `UIControl` documentation (Xcode) +* `UIControlEvents` documentation (Xcode): a list of all available control events, including the "value changed" event used in this challenge. +* How to build selectors by hand (apologies for the URL): http://fuckingselectorsyntax.com +* Examples of custom controls: Search the web for `uicontrol site:github.com` +* Custom Cocoa Control repository: https://www.cocoacontrols.com diff --git a/Sprint 6/Module 2-Custom Controls/README.md b/Sprint 6/Module 2-Custom Controls/README.md index a758563e..272fe502 100644 --- a/Sprint 6/Module 2-Custom Controls/README.md +++ b/Sprint 6/Module 2-Custom Controls/README.md @@ -2,7 +2,7 @@ Your project creates a new custom control that allows users to rate items by swiping a finger along a row of stars. This project helps you practice the concepts learned in Lambda's iOS module 6.2. -After completing the lesson material and ht is project, you'll be able to subclass UIControl to develop custom interactions. +After completing the lesson material and this project, you'll be able to subclass UIControl to develop custom interactions. Preview your project: https://youtu.be/kWtJLhX8-gw @@ -21,13 +21,13 @@ Follow these steps to set up your project skeleton: These steps walk you through creating the new control class and adding an instance in Interface Builder: 1. Create a new Swift file using File > New File. Name it CustomControl.swift. -2. In the file, import UIKit and create a new type (called `CustomControl`) that you subclass from `UIControl`. +2. In the file, import UIKit and create a new class (called `CustomControl`) that you subclass from `UIControl`. 3. Add a new Int-typed variable property called `value` to your class. It's initial value should be 1. This property is API-facing, so clients will be able to see it. It establishes your control as a value-providing (and value-changing) type. -3. In Interface builder, add a new view. Change the background color in the Attributes Inspector so it's visible. Don't worry about the color you pick. This is just to make it easier to work with. +3. In Interface builder, on your view controller, add a new view. Change the background color in the Attributes Inspector so it's visible. Don't worry about the color you pick. This is just to make it easier to work with. 4. Use the Identity Inspector to set the class to Custom Control. 5. Center it with Auto Layout but don't set any rules about size. Instead, your type will use an intrinsic size to tell Auto Layout how big it will be. 6. In the Size Inspector, select Ambiguity > Verify Position Only. This supports your "no-size" layout. -7. Use Ctrl-drag to connect your view to ViewController.swift with an IBAction. Note the "Event" pop-up currently set to "Value Changed". Look at the other options in the pop-up (like "Touch Down" and "Touch Drag Inside") but keep the event set to "Value Changed". Name your IBAction `updateRating`. This method allows the control's client (in this case your view controller) to receive updates about changes in the rating control. +7. Use `ctrl-drag` to connect your view to ViewController.swift with an IBAction. Note that the "Event" pop-up is currently set to "Value Changed". Look at the other options in the pop-up (like "Touch Down" and "Touch Drag Inside") but keep the event set to "Value Changed". Name your IBAction `updateRating`. This method allows the control's client (in this case your view controller) to receive updates about changes in the rating control. Change the Sender to `CustomControl` or follow step 8. 8. Edit the new method's signature to: `@IBAction func updateRating(_ ratingControl: CustomControl)`. This keeps you from having to cast the `sender` to the right class. 9. Implement `updateRating`. Set the view controller's title to the string `"User Rating: N stars"` where N is the number of stars. This number is the control's visible `value` property. 10. **Stretch**: Fix the title so it's correct for 1 ("star" not "stars") as well as 2-5. @@ -73,7 +73,7 @@ The `sendActions(for:)` method you'll call from these handlers is what makes a c Your code will demonstrate the kinds of events that can be subscribed to and are typical for UIControls. As a rule, always produce as many sent actions as possible because you don't know how your controls will be used in the future. The more exhaustive you are, the better the shelf life of your controls. Plus it's (1) minimal code and (2) almost boilerplate. You can reuse this code between controls with little change. -1. Add skeletons for `begin`, `continue`, `end`, and `cancel` tracking methods. The `begin` and `continue` methods should just return true. +1. Add skeletons for the `begin`, `continue`, `end`, and `cancel` tracking methods. The `begin` and `continue` methods should just return true. 2. Add a skeleton for an `updateValue(at touch: UITouch)` method. 3. In `cancel`, send an action for `.touchCancel` 4. In `begin`, add `updateValue()` to respond to the start of your user's touch. @@ -82,7 +82,7 @@ Your code will demonstrate the kinds of events that can be subscribed to and are In your code, the `end` handler generates a value update. It provides a little safety net in case the lift event has moved the finger in an untracked movement. You can omit it if desired or keep it if you feel cautious. -With value controls, there's no penalty for spawning extra `.valueChanged` events with `sendActions(for:)`. Be far more cautious with trigger controls. A button or other trigger should only send *one* primary action. For these, keep a "wasTriggered" Boolean variable on hand. Do not `sendActions` after the first control trigger. Otherwise, your angry customers may end up authorizing multiple payments or using up all the arrows in their quiver when they only intended to pay once or shoot once. +With value controls, there's no penalty for spawning extra `.valueChanged` events with `sendActions(for:)`. Be far more cautious with trigger controls. A button or other trigger should only send *one* primary action. For these, keep a "wasTriggered" Boolean variable on hand. Do not `sendActions` after the first control trigger. Otherwise, your (soon-to-be) angry customers may end up authorizing multiple payments or using up all the arrows in their quiver when they only intended to pay once or shoot once. ## Respond to touches. diff --git a/Sprint 6/Module 2-Custom Controls/Selectors in Swift.html b/Sprint 6/Module 2-Custom Controls/Selectors in Swift.html new file mode 100644 index 00000000..9905f634 --- /dev/null +++ b/Sprint 6/Module 2-Custom Controls/Selectors in Swift.html @@ -0,0 +1,171 @@ + + +Selectors in Swift + + + + +
+

Swift selector syntax

+
+
+ #selector(method name) +
+

+ Selectors in Swift are similar to selectors in Objective-C. A selector is used to refer to a method or a variable's getter or setter that is available in the Objective-C runtime. +

+

+ The value of a selector expression is an instance of the Selector + type that can be passed to functions expecting a Selector such as + perform(<selector>, with:self, afterDelay:0.5). +

+
+
+

Referencing a method on a class:

+

+ Consider the following class and its methods: +

+
class Speaker: NSObject {
+
+    @objc
+    func apologize() {
+        print("I'm sorry")
+    }
+
+    @objc
+    func shout(message: String) {
+        print("\(message.uppercased())!")
+    }
+
+    @objc
+    func scream(_ message: String) {
+        print("\(message.uppercased().characters.map { "\($0) " }.joined() )!")
+    }
+
+    @objc
+    func say(date: Date) {
+        print(DateFormatter.localizedString(from: date, dateStyle: .long, timeStyle: .none))
+    }
+
+    @objc
+    func say(text string: String) {
+        print("\(string)")
+    }
+}
+

+ In the simplest case, when there's no method overloading happening, + referencing a method on a class works by simply naming the class and + the method using dot syntax, like so: +

+
Speaker().perform(#selector(Speaker.apologize))
+Speaker().perform(#selector(Speaker.shout), with: "Hello")
+Speaker().perform(#selector(Speaker.scream), with: "Hello")
+

+ When there is only a single method with the same name + (like apologize() or + shout(message:)), you + can leave out the parentheses like we've done above. +

+

+ However, when there is ambiguity caused by method overloading like in + the case of the two variants of say(_:), + the signature needs to be more explicit: +

+
Speaker().perform(#selector(Speaker.say(text:)))
+Speaker().perform(#selector(Speaker.say(date:)))
+
+
+

Referencing a static or class method on a class:

+

+ Referencing a static or class method works exactly like instance methods. + The only difference is that the target object is the class, not an + instance of the class, as illustrated below: +

+
class Speaker: NSObject {
+    @objc
+    static func apologize() {
+        print("We are sorry")
+    }
+
+    @objc
+    class func apologizeHard() {
+        print("We are very sorry")
+    }
+}
+
+Speaker.perform(#selector(StaticSpeaker.apologize))
+Speaker.perform(#selector(StaticSpeaker.apologizeHard))
+
+
+

Referencing a getter or setter of a property:

+

+ Referencing a property to either get its value or set its value using a + Selector works through the getter: + and setter: prefixes in front of the property's name. +

+

+ Consider this Person class and its name property: +

+
class Person: NSObject {
+    @objc var name: String = ""
+
+    init(_ name: String) {
+        self.name = name
+        super.init()
+    }
+}
+

+ We can reference the setter method the Objective-C runtime generates for the + name property with + #selector(setter: Person.name) and + the equivalent getter method with + #selector(getter: Person.name), as + illustrated below: +

+
let person = Person("Bob")
+print(person.perform(#selector(getter: Person.name)).takeUnretainedValue())
+// => "Bob"
+
+person.perform(#selector(setter: Person.name), with: "Alice")
+print(person.name)
+// => "Alice"
+
+
+

Using a #keyPath selector instead of a literal keypath string:

+

+ Referencing a property to either get its value or set its value using a + Selector works through the getter: + and setter: prefixes in front of the property's name. +

+

Consider this Person class and its name property:

+
class Person: NSObject {
+    @objc var name: String = ""
+
+    init(_ name: String) {
+        self.name = name
+        super.init()
+    }
+}
+

+ We can reference the setter method the Objective-C runtime generates for the + name property with + #selector(setter: Person.name) and + the equivalent getter method with + #selector(getter: Person.name), as + illustrated below: +

+
let person = Person("Bob")
+print(person.perform(#selector(getter: Person.name)).takeUnretainedValue())
+// => "Bob"
+
+person.perform(#selector(setter: Person.name), with: "Alice")
+print(person.name)
+// => "Alice"
+
+ diff --git a/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Contents.swift b/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Contents.swift new file mode 100644 index 00000000..854a0338 --- /dev/null +++ b/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Contents.swift @@ -0,0 +1,7 @@ +//: [Previous](@previous) + +import Foundation + +var str = "Hello, playground" + +//: [Next](@next) diff --git a/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Pages/Untitled Page 2.xcplaygroundpage/Contents.swift b/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Pages/Untitled Page 2.xcplaygroundpage/Contents.swift new file mode 100644 index 00000000..069b612a --- /dev/null +++ b/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Pages/Untitled Page 2.xcplaygroundpage/Contents.swift @@ -0,0 +1,76 @@ +// Solution + +/// An unordered collection of unique elements that +/// may appear more than once in the collection. +public struct CountedSet { + /// Inserts the given element to the set, with + /// a count of 1 if the element is not yet present + /// or adding 1 to the count if it is. + @discardableResult + public mutating func insert(_ member: Element) -> Int { + _counts[member, default: 0] += 1 + return _counts[member]! + } + + /// Removes one instance of the given element from + /// the set. If the count goes to zero, the element is + /// removed from the set. If the element is not present, + /// the request is ignored. + @discardableResult + public mutating func remove(_ member: Element) -> Int { + + let memberCount = _counts[member, default: 0] + guard memberCount > 0 else { return 0 } + + guard memberCount > 1 else { _counts.removeValue(forKey: member); return 0 } + let newCount = _counts[member]! - 1 + _counts[member] = newCount + + return newCount + } + + /// Access an element count, returning 0 for any + /// element that does not appear in the counted set + public subscript(_ member: Element) -> Int { + return _counts[member, default: 0] + } + + /// Return the number of unique elements in the counted set + public var count: Int { return _counts.keys.count } + + /// A Boolean value that indicates whether the counted set is empty. + public var isEmpty: Bool { + return count == 0 + } + + internal var _counts: [Element: Int] +} + +extension CountedSet: ExpressibleByArrayLiteral { + // + // `ExpressibleByArrayLiteral` conformance + // + + /// Creates a counted set containing the elements of the given array literal. + /// + /// Do not call this initializer directly. It is used by the compiler when + /// you use an array literal. Instead, create a new counted set using an array + /// literal as its value by enclosing a comma-separated list of values in + /// square brackets. You can use an array literal anywhere a set is expected + /// by the type context. + public init(arrayLiteral elements: Element...) { + _counts = [:] + for element in elements { + _counts[element] = _counts[element, default: 0] + 1 + } + } +} + +enum Arrow { case iron, wooden, elven, dwarvish, magic, silver } +var aCountedSet = CountedSet() +aCountedSet[.iron] // 0 +var myCountedSet: CountedSet = [.iron, .magic, .iron, .silver, .iron, .iron] +myCountedSet[.iron] // 4 +myCountedSet.remove(.iron) // 3 +myCountedSet.remove(.dwarvish) // 0 +myCountedSet.remove(.magic) // 0 diff --git a/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Pages/Untitled Page.xcplaygroundpage/Contents.swift b/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Pages/Untitled Page.xcplaygroundpage/Contents.swift new file mode 100644 index 00000000..854a0338 --- /dev/null +++ b/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Pages/Untitled Page.xcplaygroundpage/Contents.swift @@ -0,0 +1,7 @@ +//: [Previous](@previous) + +import Foundation + +var str = "Hello, playground" + +//: [Next](@next) diff --git a/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Resources/Contents.swift b/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Resources/Contents.swift new file mode 100644 index 00000000..037c84cc --- /dev/null +++ b/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Resources/Contents.swift @@ -0,0 +1,37 @@ +import UIKit + +// Create a generic CountedSet struct that is constrained to Hashable elements. A counted set is an unordered collection of unique elements that may appear more than once in the collection. +struct CountedSet where Element: Hashable { + + // Use a private dictionary as your backing storage for set members and their counts. + private(set) var storage: [Element: Int] = [:] + + // Insert element + mutating func insert(_ element: Element) { + guard let item = storage[element] else { return } + storage[element] = item + 1 + } + + // Remove element + mutating func remove(_ element: Element) -> Int { + + guard let item = storage[element] else { return 0 } + + if storage[element]! > 0 { + storage[element] = item - 1 + } + + return storage[element]! + } + + // Support subscripting to look up current values, return 0 if one is not found. + subscript(_ member: Element) -> Int { + + } + + + // Count the amount of elements in the set and check if it's empty. + + // Conform to ExpressibleByArrayLiteral, perhaps via an extension + +} diff --git a/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/contents.xcplayground b/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/contents.xcplayground new file mode 100644 index 00000000..95ee11a1 --- /dev/null +++ b/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/contents.xcplayground @@ -0,0 +1,2 @@ + + \ No newline at end of file