From 8f04c939c71e320bbb23cb57ae054ef5350f7ea0 Mon Sep 17 00:00:00 2001 From: Jaspal Suri <44656000+JaspalSuri@users.noreply.github.com> Date: Tue, 8 Jan 2019 12:45:58 -0800 Subject: [PATCH 01/15] "ht is" to "this" --- Sprint 6/Module 2-Custom Controls/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sprint 6/Module 2-Custom Controls/README.md b/Sprint 6/Module 2-Custom Controls/README.md index a758563e..273ae4e6 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 From 73d080a88f12110f7211602b3cfdcb26bb53434f Mon Sep 17 00:00:00 2001 From: Jaspal Suri <44656000+JaspalSuri@users.noreply.github.com> Date: Tue, 8 Jan 2019 12:55:53 -0800 Subject: [PATCH 02/15] Initial Project Setup --- .../Custom Controls.xcodeproj/project.pbxproj | 341 ++++++++++++++++++ .../Custom Controls/AppDelegate.swift | 12 + .../AppIcon.appiconset/Contents.json | 98 +++++ .../Assets.xcassets/Contents.json | 6 + .../Base.lproj/LaunchScreen.storyboard | 25 ++ .../Base.lproj/Main.storyboard | 48 +++ .../Custom Controls/Info.plist | 45 +++ .../Custom Controls/ViewController.swift | 6 + .../Selectors in Swift.html | 171 +++++++++ 9 files changed, 752 insertions(+) create mode 100644 Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls.xcodeproj/project.pbxproj create mode 100644 Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/AppDelegate.swift create mode 100644 Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/Assets.xcassets/Contents.json create mode 100644 Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/Base.lproj/LaunchScreen.storyboard create mode 100644 Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/Base.lproj/Main.storyboard create mode 100644 Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/Info.plist create mode 100644 Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/ViewController.swift create mode 100644 Sprint 6/Module 2-Custom Controls/Selectors in Swift.html 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..73fc4d1f --- /dev/null +++ b/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls.xcodeproj/project.pbxproj @@ -0,0 +1,341 @@ +// !$*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 */; }; +/* 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 = ""; }; +/* 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 */, + 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 */, + 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..ac1c45b0 --- /dev/null +++ b/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/Base.lproj/Main.storyboard @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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..f2b37095 --- /dev/null +++ b/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/ViewController.swift @@ -0,0 +1,6 @@ +import UIKit + +class ViewController: UIViewController { + +} + 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"
+
+ From 07937eeba1e3f05747e043c61b64dbae74bf465a Mon Sep 17 00:00:00 2001 From: Jaspal Suri <44656000+JaspalSuri@users.noreply.github.com> Date: Tue, 8 Jan 2019 13:56:29 -0800 Subject: [PATCH 03/15] add Custom Control Class and view object --- .../Custom Controls.xcodeproj/project.pbxproj | 4 ++++ .../Custom Controls/Base.lproj/Main.storyboard | 13 +++++++++++++ .../Custom Controls/CustomControl.swift | 5 +++++ .../Custom Controls/ViewController.swift | 3 +++ Sprint 6/Module 2-Custom Controls/README.md | 6 +++--- 5 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/CustomControl.swift 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 index 73fc4d1f..33e84872 100644 --- 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 @@ -12,6 +12,7 @@ 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 */ @@ -22,6 +23,7 @@ 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 */ @@ -57,6 +59,7 @@ F83BD17321E5426C006148A4 /* AppDelegate.swift */, F83BD17521E5426C006148A4 /* ViewController.swift */, F83BD17721E5426C006148A4 /* Main.storyboard */, + F83BD18521E54645006148A4 /* CustomControl.swift */, F83BD17A21E5426D006148A4 /* Assets.xcassets */, F83BD17C21E5426D006148A4 /* LaunchScreen.storyboard */, F83BD17F21E5426D006148A4 /* Info.plist */, @@ -136,6 +139,7 @@ buildActionMask = 2147483647; files = ( F83BD17621E5426C006148A4 /* ViewController.swift in Sources */, + F83BD18621E54645006148A4 /* CustomControl.swift in Sources */, F83BD17421E5426C006148A4 /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; 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 index ac1c45b0..a890b531 100644 --- 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 @@ -17,7 +17,20 @@ + + + + + + + + + + + + + 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..b28215ec --- /dev/null +++ b/Sprint 6/Module 2-Custom Controls/Custom Controls/Custom Controls/CustomControl.swift @@ -0,0 +1,5 @@ +import UIKit + +class CustomControl: UIControl { + var value: Int = 1 +} 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 index f2b37095..a9fe4fb0 100644 --- 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 @@ -2,5 +2,8 @@ import UIKit class ViewController: UIViewController { + @IBAction func updateRating(_ sender: CustomControl) { + self.navigationItem.title = "User Rating: N Stars" + } } diff --git a/Sprint 6/Module 2-Custom Controls/README.md b/Sprint 6/Module 2-Custom Controls/README.md index 273ae4e6..ee1ec963 100644 --- a/Sprint 6/Module 2-Custom Controls/README.md +++ b/Sprint 6/Module 2-Custom Controls/README.md @@ -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. From 24636b37ba9a24a4827a0c5cfb25011506a74aba Mon Sep 17 00:00:00 2001 From: Jaspal Suri <44656000+JaspalSuri@users.noreply.github.com> Date: Tue, 8 Jan 2019 14:01:01 -0800 Subject: [PATCH 04/15] update the navigation title based on the amount of stars selected This currently not functional. --- .../Custom Controls/Custom Controls/ViewController.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 index a9fe4fb0..f03e3da9 100644 --- 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 @@ -3,7 +3,12 @@ import UIKit class ViewController: UIViewController { @IBAction func updateRating(_ sender: CustomControl) { - self.navigationItem.title = "User Rating: N Stars" + var starCount = 0 + if startCount == 1 { + self.navigationItem.title = "User Rating: 1 Star" + } else { + self.navigationItem.title = "User Rating: \(starCount) Stars" + } } } From 0c5a1adc9e59329d6df8ef3ad8d988591a38c743 Mon Sep 17 00:00:00 2001 From: Jaspal Suri <44656000+JaspalSuri@users.noreply.github.com> Date: Tue, 8 Jan 2019 14:43:40 -0800 Subject: [PATCH 05/15] build the control view - minor typo fixed on the VC - added pseudocode to the Custom Control --- .../Custom Controls/CustomControl.swift | 43 +++++++++++++++++++ .../Custom Controls/ViewController.swift | 4 +- 2 files changed, 45 insertions(+), 2 deletions(-) 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 index b28215ec..1418d951 100644 --- 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 @@ -2,4 +2,47 @@ 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.. Date: Tue, 8 Jan 2019 15:21:08 -0800 Subject: [PATCH 06/15] respond to touches --- .../Custom Controls/CustomControl.swift | 56 ++++++++++++++++++- Sprint 6/Module 2-Custom Controls/README.md | 4 +- 2 files changed, 57 insertions(+), 3 deletions(-) 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 index 1418d951..cc631247 100644 --- 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 @@ -29,7 +29,7 @@ class CustomControl: UIControl { } label.font = .systemBold label.fontSize = 32 - label.text = * + label.text = "*" label.alignment = center if label == active { label.textColor = componentActiveColor @@ -45,4 +45,58 @@ class CustomControl: UIControl { let width = componentsWidth + componentsSpacing return CGSize(width: width, height: componentDimension) } + + override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> 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/README.md b/Sprint 6/Module 2-Custom Controls/README.md index ee1ec963..272fe502 100644 --- a/Sprint 6/Module 2-Custom Controls/README.md +++ b/Sprint 6/Module 2-Custom Controls/README.md @@ -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. From 36a3058c363c645f0920dede73492b0c5f672f73 Mon Sep 17 00:00:00 2001 From: Jaspal Suri <44656000+JaspalSuri@users.noreply.github.com> Date: Tue, 26 Feb 2019 13:06:09 -0800 Subject: [PATCH 07/15] a random readme --- .../README-Progress.md | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 Sprint 6/Module 2-Custom Controls/README-Progress.md 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 From a6ace7d7b2cd6be65f284daf7d1d4008aeae2bd0 Mon Sep 17 00:00:00 2001 From: Jaspal Suri <44656000+JaspalSuri@users.noreply.github.com> Date: Tue, 26 Feb 2019 13:26:18 -0800 Subject: [PATCH 08/15] add playground for generics --- .../Generics Afternoon Project.playground/Contents.swift | 3 +++ .../contents.xcplayground | 4 ++++ 2 files changed, 7 insertions(+) create mode 100644 Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Contents.swift create mode 100644 Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/contents.xcplayground 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..6bfb935c --- /dev/null +++ b/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Contents.swift @@ -0,0 +1,3 @@ +import UIKit + + 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..9f5f2f40 --- /dev/null +++ b/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file From b158927662281f5763a396e0e969fff62eaf47d5 Mon Sep 17 00:00:00 2001 From: Jaspal Suri <44656000+JaspalSuri@users.noreply.github.com> Date: Tue, 26 Feb 2019 14:46:03 -0800 Subject: [PATCH 09/15] add struct --- .../Generics Afternoon Project.playground/Contents.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 index 6bfb935c..401753ee 100644 --- a/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Contents.swift +++ b/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Contents.swift @@ -1,3 +1,5 @@ import UIKit - +struct CountedSet where Generic: Hashable { + +} From 3dda60c243d5a246c7482d767ebf6a34746f9194 Mon Sep 17 00:00:00 2001 From: Jaspal Suri <44656000+JaspalSuri@users.noreply.github.com> Date: Tue, 26 Feb 2019 14:51:03 -0800 Subject: [PATCH 10/15] rename generic to element --- .../Generics Afternoon Project.playground/Contents.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 401753ee..b4231bcf 100644 --- a/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Contents.swift +++ b/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Contents.swift @@ -1,5 +1,5 @@ import UIKit -struct CountedSet where Generic: Hashable { +struct CountedSet where Element: Hashable { } From 2cf972a4607d6bc54da4e52ee0513e2bbd594895 Mon Sep 17 00:00:00 2001 From: Jaspal Suri <44656000+JaspalSuri@users.noreply.github.com> Date: Tue, 26 Feb 2019 15:08:21 -0800 Subject: [PATCH 11/15] plan out what to add --- .../Contents.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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 index b4231bcf..53250478 100644 --- a/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Contents.swift +++ b/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Contents.swift @@ -2,4 +2,16 @@ import UIKit struct CountedSet where Element: Hashable { + // Insert element + + // Remove element + + // Insert or Remove Element + + // Support subscripting to look up current values, return 0 if one is not found. + + // Count the amount of elements in the set and check if it's empty. + + // Conform to ExpressibleByArrayLiteral, perhaps via an extension + } From dd5f3005634da09b11240e4e2f419f3510474f28 Mon Sep 17 00:00:00 2001 From: Jaspal Suri <44656000+JaspalSuri@users.noreply.github.com> Date: Tue, 26 Feb 2019 15:37:01 -0800 Subject: [PATCH 12/15] add the private dictionary containing elements and their counts --- .../Generics Afternoon Project.playground/Contents.swift | 4 ++++ 1 file changed, 4 insertions(+) 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 index 53250478..51eefdc5 100644 --- a/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Contents.swift +++ b/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Contents.swift @@ -1,7 +1,11 @@ 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 // Remove element From 89b39a787319234894cd807c380553ce54e28aca Mon Sep 17 00:00:00 2001 From: Jaspal Suri <44656000+JaspalSuri@users.noreply.github.com> Date: Tue, 26 Feb 2019 15:48:23 -0800 Subject: [PATCH 13/15] add the ability to insert an element --- .../Generics Afternoon Project.playground/Contents.swift | 5 +++++ 1 file changed, 5 insertions(+) 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 index 51eefdc5..f6387a0d 100644 --- a/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Contents.swift +++ b/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Contents.swift @@ -7,9 +7,14 @@ struct CountedSet where Element: Hashable { 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 + // Insert or Remove Element // Support subscripting to look up current values, return 0 if one is not found. From 657eae642118197769446e0f356fdb97a69407ef Mon Sep 17 00:00:00 2001 From: Jaspal Suri <44656000+JaspalSuri@users.noreply.github.com> Date: Tue, 26 Feb 2019 16:12:36 -0800 Subject: [PATCH 14/15] add ability to remove elements --- .../Contents.swift | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) 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 index f6387a0d..15dc3402 100644 --- a/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Contents.swift +++ b/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Contents.swift @@ -13,7 +13,16 @@ struct CountedSet where Element: Hashable { } // 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]! + } // Insert or Remove Element From e5a9fa6b5f3f318c915de8731009d3bba3c48697 Mon Sep 17 00:00:00 2001 From: Jaspal Suri <44656000+JaspalSuri@users.noreply.github.com> Date: Tue, 26 Feb 2019 16:41:50 -0800 Subject: [PATCH 15/15] add the solution code that was provided Playgrounds crashed, so I still have my contents, file, but it's not as easily accessible. --- .../Contents.swift | 40 ++-------- .../Contents.swift | 76 +++++++++++++++++++ .../Contents.swift | 7 ++ .../Resources/Contents.swift | 37 +++++++++ .../contents.xcplayground | 4 +- 5 files changed, 127 insertions(+), 37 deletions(-) create mode 100644 Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Pages/Untitled Page 2.xcplaygroundpage/Contents.swift create mode 100644 Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Pages/Untitled Page.xcplaygroundpage/Contents.swift create mode 100644 Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Resources/Contents.swift 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 index 15dc3402..854a0338 100644 --- a/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Contents.swift +++ b/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/Contents.swift @@ -1,35 +1,7 @@ -import UIKit +//: [Previous](@previous) -// 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]! - } - - // Insert or Remove Element - - // Support subscripting to look up current values, return 0 if one is not found. - - // Count the amount of elements in the set and check if it's empty. - - // Conform to ExpressibleByArrayLiteral, perhaps via an extension - -} +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 index 9f5f2f40..95ee11a1 100644 --- a/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/contents.xcplayground +++ b/Sprint 7/Generics (7.2)/Generics Afternoon Project.playground/contents.xcplayground @@ -1,4 +1,2 @@ - - - \ No newline at end of file + \ No newline at end of file