diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5f37cdd --- /dev/null +++ b/.gitignore @@ -0,0 +1,77 @@ +### CocoaPods ### +Pods/ + +### fastlane ### +iOSInjectionProject/ + +# fastlane specific +fastlane/report.xml + +# deliver temporary files +fastlane/Preview.html + +# snapshot generated screenshots +fastlane/screenshots/**/*.png +fastlane/screenshots/screenshots.html + +# scan temporary files +fastlane/test_output + +### Xcode ### + +## Xcode Patch +*.xcodeproj/* +!*.xcodeproj/project.pbxproj +!*.xcodeproj/xcshareddata/ +!*.xcworkspace/contents.xcworkspacedata +/*.gcno + +## DS Store +.DS_Store +.DS_Store? +*.DS_Store + +### Xcode Patch ### +**/xcshareddata/WorkspaceSettings.xcsettings + +### Swift ### + +## Build generated +build/ +DerivedData/ + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ + +## Other +*.moved-aside +*.xccheckout +*.xcscmblueprint + +## Obj-C/Swift specific +*.hmap +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +.build/ + +# Carthage +Carthage/Build + +# Accio dependency management +Dependencies/ +.accio/ \ No newline at end of file diff --git a/Images/example_dark.png b/Images/example_dark.png new file mode 100644 index 0000000..e46b8b6 Binary files /dev/null and b/Images/example_dark.png differ diff --git a/Images/example_light.png b/Images/example_light.png new file mode 100644 index 0000000..1dd22e5 Binary files /dev/null and b/Images/example_light.png differ diff --git a/README.md b/README.md index 7d3755a..e11f12c 100644 --- a/README.md +++ b/README.md @@ -13,3 +13,7 @@ Here you can see a very simple way of implementing the new Diffable Datasource a ## Project 3: SwiftUI. My preferred way of writing apps lately. With just half of the number of lines of code in comparison to projects 1 and 2. + + +![](Images/example_light.png) +![](Images/example_dark.png) \ No newline at end of file diff --git a/SwiftUI/Stopwatch.xcodeproj/project.pbxproj b/SwiftUI/Stopwatch.xcodeproj/project.pbxproj new file mode 100644 index 0000000..18b8e14 --- /dev/null +++ b/SwiftUI/Stopwatch.xcodeproj/project.pbxproj @@ -0,0 +1,391 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 8F51528624842DE40016A33A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F51528524842DE40016A33A /* AppDelegate.swift */; }; + 8F51528824842DE40016A33A /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F51528724842DE40016A33A /* SceneDelegate.swift */; }; + 8F51528A24842DE40016A33A /* StopwatchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F51528924842DE40016A33A /* StopwatchView.swift */; }; + 8F51528C24842DE50016A33A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8F51528B24842DE50016A33A /* Assets.xcassets */; }; + 8F51528F24842DE50016A33A /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8F51528E24842DE50016A33A /* Preview Assets.xcassets */; }; + 8F51529224842DE50016A33A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8F51529024842DE50016A33A /* LaunchScreen.storyboard */; }; + 8F51529A248432790016A33A /* ActionButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F515299248432790016A33A /* ActionButtonView.swift */; }; + 8F51529C248449610016A33A /* LapTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F51529B248449610016A33A /* LapTime.swift */; }; + 8F51529E248453090016A33A /* DisplayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F51529D248453090016A33A /* DisplayView.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 8F51528224842DE40016A33A /* Stopwatch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Stopwatch.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 8F51528524842DE40016A33A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 8F51528724842DE40016A33A /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 8F51528924842DE40016A33A /* StopwatchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StopwatchView.swift; sourceTree = ""; }; + 8F51528B24842DE50016A33A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 8F51528E24842DE50016A33A /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 8F51529124842DE50016A33A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 8F51529324842DE50016A33A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 8F515299248432790016A33A /* ActionButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButtonView.swift; sourceTree = ""; }; + 8F51529B248449610016A33A /* LapTime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LapTime.swift; sourceTree = ""; }; + 8F51529D248453090016A33A /* DisplayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayView.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8F51527F24842DE40016A33A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 8F51527924842DE40016A33A = { + isa = PBXGroup; + children = ( + 8F51528424842DE40016A33A /* Stopwatch */, + 8F51528324842DE40016A33A /* Products */, + ); + sourceTree = ""; + }; + 8F51528324842DE40016A33A /* Products */ = { + isa = PBXGroup; + children = ( + 8F51528224842DE40016A33A /* Stopwatch.app */, + ); + name = Products; + sourceTree = ""; + }; + 8F51528424842DE40016A33A /* Stopwatch */ = { + isa = PBXGroup; + children = ( + 8F5152A1248463F30016A33A /* System */, + 8F5152A2248463FE0016A33A /* Model */, + 8F5152A3248464070016A33A /* Components */, + 8F5152A4248464150016A33A /* Screens */, + 8F51528B24842DE50016A33A /* Assets.xcassets */, + 8F51529024842DE50016A33A /* LaunchScreen.storyboard */, + 8F51529324842DE50016A33A /* Info.plist */, + 8F51528D24842DE50016A33A /* Preview Content */, + ); + path = Stopwatch; + sourceTree = ""; + }; + 8F51528D24842DE50016A33A /* Preview Content */ = { + isa = PBXGroup; + children = ( + 8F51528E24842DE50016A33A /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 8F5152A1248463F30016A33A /* System */ = { + isa = PBXGroup; + children = ( + 8F51528524842DE40016A33A /* AppDelegate.swift */, + 8F51528724842DE40016A33A /* SceneDelegate.swift */, + ); + path = System; + sourceTree = ""; + }; + 8F5152A2248463FE0016A33A /* Model */ = { + isa = PBXGroup; + children = ( + 8F51529B248449610016A33A /* LapTime.swift */, + ); + path = Model; + sourceTree = ""; + }; + 8F5152A3248464070016A33A /* Components */ = { + isa = PBXGroup; + children = ( + 8F51529D248453090016A33A /* DisplayView.swift */, + 8F515299248432790016A33A /* ActionButtonView.swift */, + ); + path = Components; + sourceTree = ""; + }; + 8F5152A4248464150016A33A /* Screens */ = { + isa = PBXGroup; + children = ( + 8F51528924842DE40016A33A /* StopwatchView.swift */, + ); + path = Screens; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 8F51528124842DE40016A33A /* Stopwatch */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8F51529624842DE50016A33A /* Build configuration list for PBXNativeTarget "Stopwatch" */; + buildPhases = ( + 8F51527E24842DE40016A33A /* Sources */, + 8F51527F24842DE40016A33A /* Frameworks */, + 8F51528024842DE40016A33A /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Stopwatch; + productName = Stopwatch; + productReference = 8F51528224842DE40016A33A /* Stopwatch.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 8F51527A24842DE40016A33A /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1150; + LastUpgradeCheck = 1150; + ORGANIZATIONNAME = "Leonardo Bilia"; + TargetAttributes = { + 8F51528124842DE40016A33A = { + CreatedOnToolsVersion = 11.5; + }; + }; + }; + buildConfigurationList = 8F51527D24842DE40016A33A /* Build configuration list for PBXProject "Stopwatch" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 8F51527924842DE40016A33A; + productRefGroup = 8F51528324842DE40016A33A /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 8F51528124842DE40016A33A /* Stopwatch */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 8F51528024842DE40016A33A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8F51529224842DE50016A33A /* LaunchScreen.storyboard in Resources */, + 8F51528F24842DE50016A33A /* Preview Assets.xcassets in Resources */, + 8F51528C24842DE50016A33A /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 8F51527E24842DE40016A33A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8F51528624842DE40016A33A /* AppDelegate.swift in Sources */, + 8F51528824842DE40016A33A /* SceneDelegate.swift in Sources */, + 8F51529C248449610016A33A /* LapTime.swift in Sources */, + 8F51529E248453090016A33A /* DisplayView.swift in Sources */, + 8F51529A248432790016A33A /* ActionButtonView.swift in Sources */, + 8F51528A24842DE40016A33A /* StopwatchView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 8F51529024842DE50016A33A /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 8F51529124842DE50016A33A /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 8F51529424842DE50016A33A /* 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; + 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 = 13.5; + 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; + }; + 8F51529524842DE50016A33A /* 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; + 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 = 13.5; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 8F51529724842DE50016A33A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"Stopwatch/Preview Content\""; + DEVELOPMENT_TEAM = C8CWPZXDG8; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = Stopwatch/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.codableme.Stopwatch; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 8F51529824842DE50016A33A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"Stopwatch/Preview Content\""; + DEVELOPMENT_TEAM = C8CWPZXDG8; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = Stopwatch/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.codableme.Stopwatch; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 8F51527D24842DE40016A33A /* Build configuration list for PBXProject "Stopwatch" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8F51529424842DE50016A33A /* Debug */, + 8F51529524842DE50016A33A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8F51529624842DE50016A33A /* Build configuration list for PBXNativeTarget "Stopwatch" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8F51529724842DE50016A33A /* Debug */, + 8F51529824842DE50016A33A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 8F51527A24842DE40016A33A /* Project object */; +} diff --git a/SwiftUI/Stopwatch.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/SwiftUI/Stopwatch.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..c935585 --- /dev/null +++ b/SwiftUI/Stopwatch.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/SwiftUI/Stopwatch.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/SwiftUI/Stopwatch.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/SwiftUI/Stopwatch.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/SwiftUI/Stopwatch/Assets.xcassets/AppIcon.appiconset/Contents.json b/SwiftUI/Stopwatch/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..9221b9b --- /dev/null +++ b/SwiftUI/Stopwatch/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SwiftUI/Stopwatch/Assets.xcassets/Contents.json b/SwiftUI/Stopwatch/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/SwiftUI/Stopwatch/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SwiftUI/Stopwatch/Base.lproj/LaunchScreen.storyboard b/SwiftUI/Stopwatch/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..865e932 --- /dev/null +++ b/SwiftUI/Stopwatch/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SwiftUI/Stopwatch/Components/ActionButtonView.swift b/SwiftUI/Stopwatch/Components/ActionButtonView.swift new file mode 100644 index 0000000..8a2338d --- /dev/null +++ b/SwiftUI/Stopwatch/Components/ActionButtonView.swift @@ -0,0 +1,52 @@ +// +// ActionButtonView.swift +// Stopwatch +// +// Created by Leonardo Bilia on 31/05/20. +// Copyright © 2020 Leonardo Bilia. All rights reserved. +// + +import SwiftUI + +struct ActionButtonView: View { + + enum Style { + case lap, reset, start, stop + + var title: String { + switch self { + case .lap: return "Lap" + case .reset: return "Reset" + case .start: return "Start" + case .stop: return "Stop" + } + } + + var color: Color { + switch self { + case .lap, .reset: return Color(.systemGray) + case .start: return Color(.systemGreen) + case .stop: return Color(.systemRed) + } + } + } + + var style: Style + + var body: some View { + HStack { + Spacer() + Text(style.title) + .foregroundColor(Color(.white)) + Spacer() + } + .frame(height: 80) + .background(style.color.cornerRadius(.infinity)) + } +} + +struct ActionButtonView_Previews: PreviewProvider { + static var previews: some View { + ActionButtonView(style: .start) + } +} diff --git a/SwiftUI/Stopwatch/Components/DisplayView.swift b/SwiftUI/Stopwatch/Components/DisplayView.swift new file mode 100644 index 0000000..ad58034 --- /dev/null +++ b/SwiftUI/Stopwatch/Components/DisplayView.swift @@ -0,0 +1,37 @@ +// +// DisplayView.swift +// Stopwatch +// +// Created by Leonardo Bilia on 31/05/20. +// Copyright © 2020 Leonardo Bilia. All rights reserved. +// + +import SwiftUI + +struct DisplayView: View { + + var minutes: String + var seconds: String + var milliseconds: String + + var body: some View { + HStack { + Text(minutes) + .frame(width: (UIScreen.main.bounds.width / 4)) + Text(":") + Text(seconds) + .frame(width: (UIScreen.main.bounds.width / 4)) + Text(":") + Text(milliseconds) + .frame(width: (UIScreen.main.bounds.width / 4)) + } + .font(.system(size: 70)) + .padding() + } +} + +struct DisplayView_Previews: PreviewProvider { + static var previews: some View { + DisplayView(minutes: "00", seconds: "00", milliseconds: "00") + } +} diff --git a/SwiftUI/Stopwatch/Info.plist b/SwiftUI/Stopwatch/Info.plist new file mode 100644 index 0000000..35579ca --- /dev/null +++ b/SwiftUI/Stopwatch/Info.plist @@ -0,0 +1,60 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UIRequiresFullScreen + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/SwiftUI/Stopwatch/Model/LapTime.swift b/SwiftUI/Stopwatch/Model/LapTime.swift new file mode 100644 index 0000000..b6c4c28 --- /dev/null +++ b/SwiftUI/Stopwatch/Model/LapTime.swift @@ -0,0 +1,14 @@ +// +// LapTime.swift +// Stopwatch +// +// Created by Leonardo Bilia on 31/05/20. +// Copyright © 2020 Leonardo Bilia. All rights reserved. +// + +import Foundation + +struct LapTime: Decodable, Hashable { + var title: String + var time: String +} diff --git a/SwiftUI/Stopwatch/Preview Content/Preview Assets.xcassets/Contents.json b/SwiftUI/Stopwatch/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/SwiftUI/Stopwatch/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SwiftUI/Stopwatch/Screens/StopwatchView.swift b/SwiftUI/Stopwatch/Screens/StopwatchView.swift new file mode 100644 index 0000000..3f52fb0 --- /dev/null +++ b/SwiftUI/Stopwatch/Screens/StopwatchView.swift @@ -0,0 +1,102 @@ +// +// StopwatchView.swift +// Stopwatch +// +// Created by Leonardo Bilia on 31/05/20. +// Copyright © 2020 Leonardo Bilia. All rights reserved. +// + +import SwiftUI + +struct StopwatchView: View { + + @State var sourceTimer: DispatchSourceTimer? + @State var lappedTimes = [LapTime]() + @State var timerOn = false + @State var milliseconds = "00" + @State var seconds = "00" + @State var minutes = "00" + @State var counter = 0.0 + + var body: some View { + VStack(spacing: 0) { + Spacer() + DisplayView(minutes: minutes, seconds: seconds, milliseconds: milliseconds) + + Spacer() + HStack(spacing: 8) { + Button(action: { self.lapHandler() }) { + ActionButtonView(style: .lap) + } + + Button(action: { self.resetHandler() }) { + ActionButtonView(style: .reset) + } + + Button(action: { self.startStopHandler() }) { + ActionButtonView(style: timerOn ? .stop : .start) + } + } + .padding(.horizontal) + List(lappedTimes, id: \.self) { lap in + HStack { + Text(lap.title) + Spacer() + Text(lap.time) + } + } + .frame(height: UIScreen.main.bounds.height / 2.2) + } + } + + // MARK: - Methods + + func startStopHandler() { + if timerOn { + sourceTimer?.suspend() + } else { + self.sourceTimer = DispatchSource.makeTimerSource(flags: .strict, queue: .main) + self.sourceTimer?.setEventHandler { self.updateTimer() } + self.sourceTimer?.schedule(deadline: .now(), repeating: 0.001) + self.sourceTimer?.resume() + } + timerOn.toggle() + } + + func updateTimer() { + counter += 0.001 + let millisecond = counter * 1000 + let remaingMilliseconds = (Int(millisecond) % 1000) / 10 + let second = (Int(millisecond) / 1000) % 60 + let minute = (Int(millisecond) / 1000) / 60 % 60 + + DispatchQueue.main.async { + self.minutes = String(format: "%02i", minute) + self.seconds = String(format: "%02i", second) + self.milliseconds = String(format: "%02i", remaingMilliseconds) + } + } + + func lapHandler() { + let laps = lappedTimes.count + 1 + lappedTimes.append(LapTime(title: "Lap \(laps)", time: "\(minutes):\(seconds):\(milliseconds)")) + } + + func resetHandler() { + sourceTimer?.suspend() + lappedTimes.removeAll() + timerOn = false + DispatchQueue.main.async { + self.counter = 0 + self.minutes = "00" + self.seconds = "00" + self.milliseconds = "00" + } + } +} + +struct StopwatchView_Previews: PreviewProvider { + static var previews: some View { + StopwatchView() + } +} diff --git a/SwiftUI/Stopwatch/System/AppDelegate.swift b/SwiftUI/Stopwatch/System/AppDelegate.swift new file mode 100644 index 0000000..d230eb4 --- /dev/null +++ b/SwiftUI/Stopwatch/System/AppDelegate.swift @@ -0,0 +1,34 @@ +// +// AppDelegate.swift +// Stopwatch +// +// Created by Leonardo Bilia on 31/05/20. +// Copyright © 2020 Leonardo Bilia. All rights reserved. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + static var orientationLock = UIInterfaceOrientationMask.portrait + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + return true + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + + } + + func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask { + return AppDelegate.orientationLock + } +} + diff --git a/SwiftUI/Stopwatch/System/SceneDelegate.swift b/SwiftUI/Stopwatch/System/SceneDelegate.swift new file mode 100644 index 0000000..184c9a6 --- /dev/null +++ b/SwiftUI/Stopwatch/System/SceneDelegate.swift @@ -0,0 +1,44 @@ +// +// SceneDelegate.swift +// Stopwatch +// +// Created by Leonardo Bilia on 31/05/20. +// Copyright © 2020 Leonardo Bilia. All rights reserved. +// + +import UIKit +import SwiftUI + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + guard let windowScene = scene as? UIWindowScene else { return } + let window = UIWindow(windowScene: windowScene) + window.rootViewController = UIHostingController(rootView: StopwatchView()) + self.window = window + window.makeKeyAndVisible() + } + + func sceneDidDisconnect(_ scene: UIScene) { + + } + + func sceneDidBecomeActive(_ scene: UIScene) { + + } + + func sceneWillResignActive(_ scene: UIScene) { + + } + + func sceneWillEnterForeground(_ scene: UIScene) { + + } + + func sceneDidEnterBackground(_ scene: UIScene) { + + } +} + diff --git a/UIKit - DiffableDatasource/Stopwatch.xcodeproj/project.pbxproj b/UIKit - DiffableDatasource/Stopwatch.xcodeproj/project.pbxproj new file mode 100644 index 0000000..79ed133 --- /dev/null +++ b/UIKit - DiffableDatasource/Stopwatch.xcodeproj/project.pbxproj @@ -0,0 +1,391 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 8F515257248420B20016A33A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F515256248420B20016A33A /* AppDelegate.swift */; }; + 8F515259248420B20016A33A /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F515258248420B20016A33A /* SceneDelegate.swift */; }; + 8F51525B248420B20016A33A /* StopwatchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F51525A248420B20016A33A /* StopwatchViewController.swift */; }; + 8F515260248420B30016A33A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8F51525F248420B30016A33A /* Assets.xcassets */; }; + 8F515263248420B30016A33A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8F515261248420B30016A33A /* LaunchScreen.storyboard */; }; + 8F51526F248421C60016A33A /* SWButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F51526E248421C60016A33A /* SWButton.swift */; }; + 8F515271248421DA0016A33A /* SWLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F515270248421D90016A33A /* SWLabel.swift */; }; + 8F515273248421EF0016A33A /* SWStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F515272248421EF0016A33A /* SWStackView.swift */; }; + 8F5152752484220E0016A33A /* LapTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F5152742484220E0016A33A /* LapTableViewCell.swift */; }; + 8F5152782484239E0016A33A /* LapTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F5152772484239E0016A33A /* LapTime.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 8F515253248420B20016A33A /* Stopwatch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Stopwatch.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 8F515256248420B20016A33A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 8F515258248420B20016A33A /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 8F51525A248420B20016A33A /* StopwatchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StopwatchViewController.swift; sourceTree = ""; }; + 8F51525F248420B30016A33A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 8F515262248420B30016A33A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 8F515264248420B30016A33A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 8F51526E248421C60016A33A /* SWButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SWButton.swift; sourceTree = ""; }; + 8F515270248421D90016A33A /* SWLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SWLabel.swift; sourceTree = ""; }; + 8F515272248421EF0016A33A /* SWStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SWStackView.swift; sourceTree = ""; }; + 8F5152742484220E0016A33A /* LapTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LapTableViewCell.swift; sourceTree = ""; }; + 8F5152772484239E0016A33A /* LapTime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LapTime.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8F515250248420B20016A33A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 8F51524A248420B20016A33A = { + isa = PBXGroup; + children = ( + 8F515255248420B20016A33A /* Stopwatch */, + 8F515254248420B20016A33A /* Products */, + ); + sourceTree = ""; + }; + 8F515254248420B20016A33A /* Products */ = { + isa = PBXGroup; + children = ( + 8F515253248420B20016A33A /* Stopwatch.app */, + ); + name = Products; + sourceTree = ""; + }; + 8F515255248420B20016A33A /* Stopwatch */ = { + isa = PBXGroup; + children = ( + 8F51526A2484216B0016A33A /* System */, + 8F51526B2484217D0016A33A /* Components */, + 8F515276248423690016A33A /* Model */, + 8F51526C2484218A0016A33A /* Cells */, + 8F51526D248421970016A33A /* Controllers */, + 8F51525F248420B30016A33A /* Assets.xcassets */, + 8F515261248420B30016A33A /* LaunchScreen.storyboard */, + 8F515264248420B30016A33A /* Info.plist */, + ); + path = Stopwatch; + sourceTree = ""; + }; + 8F51526A2484216B0016A33A /* System */ = { + isa = PBXGroup; + children = ( + 8F515256248420B20016A33A /* AppDelegate.swift */, + 8F515258248420B20016A33A /* SceneDelegate.swift */, + ); + path = System; + sourceTree = ""; + }; + 8F51526B2484217D0016A33A /* Components */ = { + isa = PBXGroup; + children = ( + 8F51526E248421C60016A33A /* SWButton.swift */, + 8F515270248421D90016A33A /* SWLabel.swift */, + 8F515272248421EF0016A33A /* SWStackView.swift */, + ); + path = Components; + sourceTree = ""; + }; + 8F51526C2484218A0016A33A /* Cells */ = { + isa = PBXGroup; + children = ( + 8F5152742484220E0016A33A /* LapTableViewCell.swift */, + ); + path = Cells; + sourceTree = ""; + }; + 8F51526D248421970016A33A /* Controllers */ = { + isa = PBXGroup; + children = ( + 8F51525A248420B20016A33A /* StopwatchViewController.swift */, + ); + path = Controllers; + sourceTree = ""; + }; + 8F515276248423690016A33A /* Model */ = { + isa = PBXGroup; + children = ( + 8F5152772484239E0016A33A /* LapTime.swift */, + ); + path = Model; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 8F515252248420B20016A33A /* Stopwatch */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8F515267248420B30016A33A /* Build configuration list for PBXNativeTarget "Stopwatch" */; + buildPhases = ( + 8F51524F248420B20016A33A /* Sources */, + 8F515250248420B20016A33A /* Frameworks */, + 8F515251248420B20016A33A /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Stopwatch; + productName = Stopwatch; + productReference = 8F515253248420B20016A33A /* Stopwatch.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 8F51524B248420B20016A33A /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1150; + LastUpgradeCheck = 1150; + ORGANIZATIONNAME = "Leonardo Bilia"; + TargetAttributes = { + 8F515252248420B20016A33A = { + CreatedOnToolsVersion = 11.5; + }; + }; + }; + buildConfigurationList = 8F51524E248420B20016A33A /* Build configuration list for PBXProject "Stopwatch" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 8F51524A248420B20016A33A; + productRefGroup = 8F515254248420B20016A33A /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 8F515252248420B20016A33A /* Stopwatch */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 8F515251248420B20016A33A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8F515263248420B30016A33A /* LaunchScreen.storyboard in Resources */, + 8F515260248420B30016A33A /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 8F51524F248420B20016A33A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8F51525B248420B20016A33A /* StopwatchViewController.swift in Sources */, + 8F515273248421EF0016A33A /* SWStackView.swift in Sources */, + 8F515271248421DA0016A33A /* SWLabel.swift in Sources */, + 8F515257248420B20016A33A /* AppDelegate.swift in Sources */, + 8F5152782484239E0016A33A /* LapTime.swift in Sources */, + 8F5152752484220E0016A33A /* LapTableViewCell.swift in Sources */, + 8F515259248420B20016A33A /* SceneDelegate.swift in Sources */, + 8F51526F248421C60016A33A /* SWButton.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 8F515261248420B30016A33A /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 8F515262248420B30016A33A /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 8F515265248420B30016A33A /* 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; + 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 = 13.5; + 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; + }; + 8F515266248420B30016A33A /* 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; + 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 = 13.5; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 8F515268248420B30016A33A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = C8CWPZXDG8; + INFOPLIST_FILE = Stopwatch/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.codableme.Stopwatch; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 8F515269248420B30016A33A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = C8CWPZXDG8; + INFOPLIST_FILE = Stopwatch/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.codableme.Stopwatch; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 8F51524E248420B20016A33A /* Build configuration list for PBXProject "Stopwatch" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8F515265248420B30016A33A /* Debug */, + 8F515266248420B30016A33A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8F515267248420B30016A33A /* Build configuration list for PBXNativeTarget "Stopwatch" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8F515268248420B30016A33A /* Debug */, + 8F515269248420B30016A33A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 8F51524B248420B20016A33A /* Project object */; +} diff --git a/UIKit - DiffableDatasource/Stopwatch.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/UIKit - DiffableDatasource/Stopwatch.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..c935585 --- /dev/null +++ b/UIKit - DiffableDatasource/Stopwatch.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/UIKit - DiffableDatasource/Stopwatch.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/UIKit - DiffableDatasource/Stopwatch.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/UIKit - DiffableDatasource/Stopwatch.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/UIKit - DiffableDatasource/Stopwatch/Assets.xcassets/AppIcon.appiconset/Contents.json b/UIKit - DiffableDatasource/Stopwatch/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..9221b9b --- /dev/null +++ b/UIKit - DiffableDatasource/Stopwatch/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/UIKit - DiffableDatasource/Stopwatch/Assets.xcassets/Contents.json b/UIKit - DiffableDatasource/Stopwatch/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/UIKit - DiffableDatasource/Stopwatch/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/UIKit - DiffableDatasource/Stopwatch/Base.lproj/LaunchScreen.storyboard b/UIKit - DiffableDatasource/Stopwatch/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..865e932 --- /dev/null +++ b/UIKit - DiffableDatasource/Stopwatch/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UIKit - DiffableDatasource/Stopwatch/Cells/LapTableViewCell.swift b/UIKit - DiffableDatasource/Stopwatch/Cells/LapTableViewCell.swift new file mode 100644 index 0000000..ed99c0c --- /dev/null +++ b/UIKit - DiffableDatasource/Stopwatch/Cells/LapTableViewCell.swift @@ -0,0 +1,56 @@ +// +// LapTableViewCell.swift +// Stopwatch +// +// Created by Leonardo Bilia on 31/05/20. +// Copyright © 2020 Leonardo Bilia. All rights reserved. +// + +import UIKit + +class LapTableViewCell: UITableViewCell { + + private lazy var lapLabel = UILabel() + private lazy var timerLabel = UILabel() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setupComponents() + setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Methods + + func populate(lap: LapTime) { + lapLabel.text = lap.title + timerLabel.text = lap.time + } + + func setupComponents() { + lapLabel.translatesAutoresizingMaskIntoConstraints = false + timerLabel.translatesAutoresizingMaskIntoConstraints = false + } +} + +// MARK: - UI + +extension LapTableViewCell { + + private func setupUI() { + + contentView.addSubview(lapLabel) + contentView.addSubview(timerLabel) + + NSLayoutConstraint.activate([ + lapLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), + lapLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), + + timerLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), + timerLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16) + ]) + } +} diff --git a/UIKit - DiffableDatasource/Stopwatch/Components/SWButton.swift b/UIKit - DiffableDatasource/Stopwatch/Components/SWButton.swift new file mode 100644 index 0000000..126dd2b --- /dev/null +++ b/UIKit - DiffableDatasource/Stopwatch/Components/SWButton.swift @@ -0,0 +1,28 @@ +// +// SWButton.swift +// Stopwatch +// +// Created by Leonardo Bilia on 31/05/20. +// Copyright © 2020 Leonardo Bilia. All rights reserved. +// + +import UIKit + +class SWButton: UIButton { + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setup(title: String, color: UIColor) { + self.setTitle(title, for: .normal) + self.setTitleColor(UIColor.white, for: .normal) + self.layer.cornerRadius = 40 + self.layer.masksToBounds = true + self.backgroundColor = color + } +} diff --git a/UIKit - DiffableDatasource/Stopwatch/Components/SWLabel.swift b/UIKit - DiffableDatasource/Stopwatch/Components/SWLabel.swift new file mode 100644 index 0000000..fad08f3 --- /dev/null +++ b/UIKit - DiffableDatasource/Stopwatch/Components/SWLabel.swift @@ -0,0 +1,26 @@ +// +// SWLabel.swift +// Stopwatch +// +// Created by Leonardo Bilia on 31/05/20. +// Copyright © 2020 Leonardo Bilia. All rights reserved. +// + +import UIKit + +class SWLabel: UILabel { + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setup(title: String) { + self.text = title + self.font = UIFont.systemFont(ofSize: 75) + self.textAlignment = .center + } +} diff --git a/UIKit - DiffableDatasource/Stopwatch/Components/SWStackView.swift b/UIKit - DiffableDatasource/Stopwatch/Components/SWStackView.swift new file mode 100644 index 0000000..52f68e7 --- /dev/null +++ b/UIKit - DiffableDatasource/Stopwatch/Components/SWStackView.swift @@ -0,0 +1,26 @@ +// +// SWStackView.swift +// Stopwatch +// +// Created by Leonardo Bilia on 31/05/20. +// Copyright © 2020 Leonardo Bilia. All rights reserved. +// + +import UIKit + +class SWStackView: UIStackView { + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setup(spacing: CGFloat, distribution: UIStackView.Distribution) { + self.axis = .horizontal + self.spacing = spacing + self.distribution = distribution + } +} diff --git a/UIKit - DiffableDatasource/Stopwatch/Controllers/StopwatchViewController.swift b/UIKit - DiffableDatasource/Stopwatch/Controllers/StopwatchViewController.swift new file mode 100644 index 0000000..d943448 --- /dev/null +++ b/UIKit - DiffableDatasource/Stopwatch/Controllers/StopwatchViewController.swift @@ -0,0 +1,202 @@ +// +// StopwatchViewController.swift +// Stopwatch +// +// Created by Leonardo Bilia on 31/05/20. +// Copyright © 2020 Leonardo Bilia. All rights reserved. +// + +import UIKit + +class StopwatchViewController: UIViewController { + + private let reusableIdentifier = "SWCell" + private lazy var tableView = UITableView() + + private lazy var minuteLabel = SWLabel() + private lazy var secondLabel = SWLabel() + private lazy var millisecondLabel = SWLabel() + private lazy var primarySeparatorLabel = SWLabel() + private lazy var secondarySeparatorLabel = SWLabel() + + private lazy var lapButton = SWButton() + private lazy var resetButton = SWButton() + private lazy var startStopButton = SWButton() + + private lazy var labelStackView = SWStackView() + private lazy var buttonStackView = SWStackView() + + private lazy var lappedTimes = [LapTime]() + private lazy var counter = 0.001 + private lazy var timerOn = false + private var timer = Timer() + + private var dataSource: UITableViewDiffableDataSource? + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + setupTableView() + setupDataSource() + setupComponents() + setupUI() + } + + // MARK: - Actions + + @objc private func timerHandler() { + counter += 0.001 + + let millisecond = counter * 1000 + let remaingMilliseconds = Int((Int(millisecond) % 1000) / 10) + let second = Int((Int(millisecond) / 1000) % 60) + let minute = Int((Int(millisecond) / 1000) / 60 % 60) + + DispatchQueue.main.async { + self.minuteLabel.text = String(format: "%02i", minute) + self.secondLabel.text = String(format: "%02i", second) + self.millisecondLabel.text = String(format: "%02i", remaingMilliseconds) + } + } + + @objc private func startStopHandler() { + if timerOn { + timer.invalidate() + } else { + let timer = Timer(timeInterval: 0.001, target: self, selector: #selector(timerHandler), userInfo: nil, repeats: true) + RunLoop.current.add(timer, forMode: .common) + timer.tolerance = 0.0001 + self.timer = timer + } + self.timerOn.toggle() + DispatchQueue.main.async { + self.startStopButton.setTitle(self.timerOn ? "Stop" : "Start", for: .normal) + self.startStopButton.backgroundColor = self.timerOn ? .systemRed : .systemGreen + } + } + + @objc private func lapHandler() { + guard let minute = minuteLabel.text, let second = secondLabel.text, let millisecond = millisecondLabel.text, timerOn else { return } + + let indexPath = IndexPath(row: lappedTimes.count, section: 0) + lappedTimes.append(LapTime(title: "Lap \(indexPath.row + 1)", time: "\(minute):\(second):\(millisecond)")) + snapshot(from: lappedTimes) + } + + @objc private func resetHandler() { + lappedTimes.removeAll() + timerOn = false + counter = 0 + timer.invalidate() + snapshot(from: lappedTimes) + + DispatchQueue.main.async { + self.minuteLabel.setup(title: "00") + self.secondLabel.setup(title: "00") + self.millisecondLabel.setup(title: "00") + self.startStopButton.setTitle("Start", for: .normal) + self.startStopButton.backgroundColor = .systemGreen + } + } + + // MARK: - Methods + + func setupTableView() { + tableView.register(LapTableViewCell.self, forCellReuseIdentifier: reusableIdentifier) + tableView.estimatedRowHeight = 50 + tableView.rowHeight = 50 + } + + func setupComponents() { + minuteLabel.setup(title: "00") + secondLabel.setup(title: "00") + millisecondLabel.setup(title: "00") + primarySeparatorLabel.setup(title: ":") + secondarySeparatorLabel.setup(title: ":") + + labelStackView.setup(spacing: 0, distribution: .equalCentering) + buttonStackView.setup(spacing: 16, distribution: .fillEqually) + + lapButton.setup(title: "Lap", color: .secondaryLabel) + lapButton.addTarget(self, action: #selector(lapHandler), for: .touchUpInside) + + resetButton.setup(title: "Reset", color: .secondaryLabel) + resetButton.addTarget(self, action: #selector(resetHandler), for: .touchUpInside) + + startStopButton.setup(title: "Start", color: .systemGreen) + startStopButton.addTarget(self, action: #selector(startStopHandler), for: .touchUpInside) + } +} + +// MARK: - Table View Diffable Data Sources + +extension StopwatchViewController { + + func setupDataSource() { + dataSource = UITableViewDiffableDataSource(tableView: tableView, cellProvider: { (tableView, indexPath, laptime) -> LapTableViewCell? in + let cell = tableView.dequeueReusableCell(withIdentifier: self.reusableIdentifier, for: indexPath) as! LapTableViewCell + cell.selectionStyle = .none + cell.populate(lap: laptime) + return cell + }) + } + + func snapshot(from lappedTimes: [LapTime]) { + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.main]) + snapshot.appendItems(lappedTimes, toSection: .main) + dataSource?.apply(snapshot, animatingDifferences: true) + } +} + + +// MARK: - UI + +extension StopwatchViewController { + + func setupUI() { + view.backgroundColor = .systemBackground + + view.addSubview(tableView) + view.addSubview(minuteLabel) + view.addSubview(buttonStackView) + buttonStackView.addArrangedSubview(lapButton) + buttonStackView.addArrangedSubview(resetButton) + buttonStackView.addArrangedSubview(startStopButton) + + view.addSubview(labelStackView) + labelStackView.addArrangedSubview(minuteLabel) + labelStackView.addArrangedSubview(primarySeparatorLabel) + labelStackView.addArrangedSubview(secondLabel) + labelStackView.addArrangedSubview(secondarySeparatorLabel) + labelStackView.addArrangedSubview(millisecondLabel) + + view.subviews.forEach({ element in + element.translatesAutoresizingMaskIntoConstraints = false + }) + + let width: CGFloat = (UIScreen.main.bounds.width / 3) - 28 + NSLayoutConstraint.activate([ + labelStackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + labelStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16), + labelStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16), + + minuteLabel.widthAnchor.constraint(equalToConstant: width), + secondLabel.widthAnchor.constraint(equalToConstant: width), + millisecondLabel.widthAnchor.constraint(equalToConstant: width), + + buttonStackView.topAnchor.constraint(equalTo: labelStackView.bottomAnchor), + buttonStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16), + buttonStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16), + buttonStackView.bottomAnchor.constraint(equalTo: view.centerYAnchor), + buttonStackView.heightAnchor.constraint(equalToConstant: 80), + + tableView.topAnchor.constraint(equalTo: buttonStackView.bottomAnchor, constant: 8), + tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor) + ]) + } +} + diff --git a/UIKit - DiffableDatasource/Stopwatch/Info.plist b/UIKit - DiffableDatasource/Stopwatch/Info.plist new file mode 100644 index 0000000..35579ca --- /dev/null +++ b/UIKit - DiffableDatasource/Stopwatch/Info.plist @@ -0,0 +1,60 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UIRequiresFullScreen + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/UIKit - DiffableDatasource/Stopwatch/Model/LapTime.swift b/UIKit - DiffableDatasource/Stopwatch/Model/LapTime.swift new file mode 100644 index 0000000..9197671 --- /dev/null +++ b/UIKit - DiffableDatasource/Stopwatch/Model/LapTime.swift @@ -0,0 +1,18 @@ +// +// LapTime.swift +// Stopwatch +// +// Created by Leonardo Bilia on 31/05/20. +// Copyright © 2020 Leonardo Bilia. All rights reserved. +// + +import Foundation + +enum Section: CaseIterable { + case main +} + +struct LapTime: Decodable, Hashable { + var title: String + var time: String +} diff --git a/UIKit - DiffableDatasource/Stopwatch/System/AppDelegate.swift b/UIKit - DiffableDatasource/Stopwatch/System/AppDelegate.swift new file mode 100644 index 0000000..7f4da6d --- /dev/null +++ b/UIKit - DiffableDatasource/Stopwatch/System/AppDelegate.swift @@ -0,0 +1,34 @@ +// +// AppDelegate.swift +// Stopwatch +// +// Created by Leonardo Bilia on 31/05/20. +// Copyright © 2020 Leonardo Bilia. All rights reserved. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + static var orientationLock = UIInterfaceOrientationMask.portrait + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + return true + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + + } + + func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask { + return AppDelegate.orientationLock + } +} + diff --git a/UIKit - DiffableDatasource/Stopwatch/System/SceneDelegate.swift b/UIKit - DiffableDatasource/Stopwatch/System/SceneDelegate.swift new file mode 100644 index 0000000..0a11842 --- /dev/null +++ b/UIKit - DiffableDatasource/Stopwatch/System/SceneDelegate.swift @@ -0,0 +1,43 @@ +// +// SceneDelegate.swift +// Stopwatch +// +// Created by Leonardo Bilia on 31/05/20. +// Copyright © 2020 Leonardo Bilia. All rights reserved. +// + +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + guard let windowScene = scene as? UIWindowScene else { return } + let window = UIWindow(windowScene: windowScene) + window.rootViewController = StopwatchViewController() + self.window = window + window.makeKeyAndVisible() + } + + func sceneDidDisconnect(_ scene: UIScene) { + + } + + func sceneDidBecomeActive(_ scene: UIScene) { + + } + + func sceneWillResignActive(_ scene: UIScene) { + + } + + func sceneWillEnterForeground(_ scene: UIScene) { + + } + + func sceneDidEnterBackground(_ scene: UIScene) { + + } +} + diff --git a/UIKit - Traditional/Stopwatch.xcodeproj/project.pbxproj b/UIKit - Traditional/Stopwatch.xcodeproj/project.pbxproj new file mode 100644 index 0000000..3b4f11f --- /dev/null +++ b/UIKit - Traditional/Stopwatch.xcodeproj/project.pbxproj @@ -0,0 +1,391 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 8F1ABE2E2483D45100721E94 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F1ABE2D2483D45100721E94 /* AppDelegate.swift */; }; + 8F1ABE302483D45100721E94 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F1ABE2F2483D45100721E94 /* SceneDelegate.swift */; }; + 8F1ABE322483D45100721E94 /* StopwatchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F1ABE312483D45100721E94 /* StopwatchViewController.swift */; }; + 8F1ABE372483D45200721E94 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8F1ABE362483D45200721E94 /* Assets.xcassets */; }; + 8F1ABE3A2483D45200721E94 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8F1ABE382483D45200721E94 /* LaunchScreen.storyboard */; }; + 8F1ABE492483E36000721E94 /* LapTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F1ABE482483E36000721E94 /* LapTableViewCell.swift */; }; + 8F1ABE4E2484153600721E94 /* SWButtom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F1ABE4D2484153600721E94 /* SWButtom.swift */; }; + 8F1ABE50248418D600721E94 /* SWLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F1ABE4F248418D600721E94 /* SWLabel.swift */; }; + 8F1ABE5224841E7000721E94 /* SWStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F1ABE5124841E7000721E94 /* SWStackView.swift */; }; + 8F5152A7248464AA0016A33A /* LapTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F5152A6248464AA0016A33A /* LapTime.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 8F1ABE2A2483D45100721E94 /* Stopwatch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Stopwatch.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 8F1ABE2D2483D45100721E94 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 8F1ABE2F2483D45100721E94 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 8F1ABE312483D45100721E94 /* StopwatchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StopwatchViewController.swift; sourceTree = ""; }; + 8F1ABE362483D45200721E94 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 8F1ABE392483D45200721E94 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 8F1ABE3B2483D45200721E94 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 8F1ABE482483E36000721E94 /* LapTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LapTableViewCell.swift; sourceTree = ""; }; + 8F1ABE4D2484153600721E94 /* SWButtom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SWButtom.swift; sourceTree = ""; }; + 8F1ABE4F248418D600721E94 /* SWLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SWLabel.swift; sourceTree = ""; }; + 8F1ABE5124841E7000721E94 /* SWStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SWStackView.swift; sourceTree = ""; }; + 8F5152A6248464AA0016A33A /* LapTime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LapTime.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8F1ABE272483D45100721E94 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 8F1ABE212483D45100721E94 = { + isa = PBXGroup; + children = ( + 8F1ABE2C2483D45100721E94 /* Stopwatch */, + 8F1ABE2B2483D45100721E94 /* Products */, + ); + sourceTree = ""; + }; + 8F1ABE2B2483D45100721E94 /* Products */ = { + isa = PBXGroup; + children = ( + 8F1ABE2A2483D45100721E94 /* Stopwatch.app */, + ); + name = Products; + sourceTree = ""; + }; + 8F1ABE2C2483D45100721E94 /* Stopwatch */ = { + isa = PBXGroup; + children = ( + 8F1ABE422483D80100721E94 /* System */, + 8F1ABE4A248414C900721E94 /* Components */, + 8F5152A52484648A0016A33A /* Model */, + 8F1ABE4B248414D700721E94 /* Cells */, + 8F1ABE4C248414E600721E94 /* Controllers */, + 8F1ABE362483D45200721E94 /* Assets.xcassets */, + 8F1ABE382483D45200721E94 /* LaunchScreen.storyboard */, + 8F1ABE3B2483D45200721E94 /* Info.plist */, + ); + path = Stopwatch; + sourceTree = ""; + }; + 8F1ABE422483D80100721E94 /* System */ = { + isa = PBXGroup; + children = ( + 8F1ABE2D2483D45100721E94 /* AppDelegate.swift */, + 8F1ABE2F2483D45100721E94 /* SceneDelegate.swift */, + ); + path = System; + sourceTree = ""; + }; + 8F1ABE4A248414C900721E94 /* Components */ = { + isa = PBXGroup; + children = ( + 8F1ABE4D2484153600721E94 /* SWButtom.swift */, + 8F1ABE4F248418D600721E94 /* SWLabel.swift */, + 8F1ABE5124841E7000721E94 /* SWStackView.swift */, + ); + path = Components; + sourceTree = ""; + }; + 8F1ABE4B248414D700721E94 /* Cells */ = { + isa = PBXGroup; + children = ( + 8F1ABE482483E36000721E94 /* LapTableViewCell.swift */, + ); + path = Cells; + sourceTree = ""; + }; + 8F1ABE4C248414E600721E94 /* Controllers */ = { + isa = PBXGroup; + children = ( + 8F1ABE312483D45100721E94 /* StopwatchViewController.swift */, + ); + path = Controllers; + sourceTree = ""; + }; + 8F5152A52484648A0016A33A /* Model */ = { + isa = PBXGroup; + children = ( + 8F5152A6248464AA0016A33A /* LapTime.swift */, + ); + path = Model; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 8F1ABE292483D45100721E94 /* Stopwatch */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8F1ABE3E2483D45200721E94 /* Build configuration list for PBXNativeTarget "Stopwatch" */; + buildPhases = ( + 8F1ABE262483D45100721E94 /* Sources */, + 8F1ABE272483D45100721E94 /* Frameworks */, + 8F1ABE282483D45100721E94 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Stopwatch; + productName = Stopwatch; + productReference = 8F1ABE2A2483D45100721E94 /* Stopwatch.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 8F1ABE222483D45100721E94 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1150; + LastUpgradeCheck = 1150; + ORGANIZATIONNAME = "Leonardo Bilia"; + TargetAttributes = { + 8F1ABE292483D45100721E94 = { + CreatedOnToolsVersion = 11.5; + }; + }; + }; + buildConfigurationList = 8F1ABE252483D45100721E94 /* Build configuration list for PBXProject "Stopwatch" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 8F1ABE212483D45100721E94; + productRefGroup = 8F1ABE2B2483D45100721E94 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 8F1ABE292483D45100721E94 /* Stopwatch */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 8F1ABE282483D45100721E94 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8F1ABE3A2483D45200721E94 /* LaunchScreen.storyboard in Resources */, + 8F1ABE372483D45200721E94 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 8F1ABE262483D45100721E94 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8F1ABE50248418D600721E94 /* SWLabel.swift in Sources */, + 8F1ABE4E2484153600721E94 /* SWButtom.swift in Sources */, + 8F1ABE322483D45100721E94 /* StopwatchViewController.swift in Sources */, + 8F1ABE492483E36000721E94 /* LapTableViewCell.swift in Sources */, + 8F5152A7248464AA0016A33A /* LapTime.swift in Sources */, + 8F1ABE5224841E7000721E94 /* SWStackView.swift in Sources */, + 8F1ABE2E2483D45100721E94 /* AppDelegate.swift in Sources */, + 8F1ABE302483D45100721E94 /* SceneDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 8F1ABE382483D45200721E94 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 8F1ABE392483D45200721E94 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 8F1ABE3C2483D45200721E94 /* 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; + 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 = 13.5; + 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; + }; + 8F1ABE3D2483D45200721E94 /* 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; + 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 = 13.5; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 8F1ABE3F2483D45200721E94 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = C8CWPZXDG8; + INFOPLIST_FILE = Stopwatch/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.codableme.Stopwatch; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 8F1ABE402483D45200721E94 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = C8CWPZXDG8; + INFOPLIST_FILE = Stopwatch/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.codableme.Stopwatch; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 8F1ABE252483D45100721E94 /* Build configuration list for PBXProject "Stopwatch" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8F1ABE3C2483D45200721E94 /* Debug */, + 8F1ABE3D2483D45200721E94 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8F1ABE3E2483D45200721E94 /* Build configuration list for PBXNativeTarget "Stopwatch" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8F1ABE3F2483D45200721E94 /* Debug */, + 8F1ABE402483D45200721E94 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 8F1ABE222483D45100721E94 /* Project object */; +} diff --git a/UIKit - Traditional/Stopwatch.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/UIKit - Traditional/Stopwatch.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..c935585 --- /dev/null +++ b/UIKit - Traditional/Stopwatch.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/UIKit - Traditional/Stopwatch.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/UIKit - Traditional/Stopwatch.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/UIKit - Traditional/Stopwatch.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/UIKit - Traditional/Stopwatch/Assets.xcassets/AppIcon.appiconset/Contents.json b/UIKit - Traditional/Stopwatch/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..9221b9b --- /dev/null +++ b/UIKit - Traditional/Stopwatch/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/UIKit - Traditional/Stopwatch/Assets.xcassets/Contents.json b/UIKit - Traditional/Stopwatch/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/UIKit - Traditional/Stopwatch/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/UIKit - Traditional/Stopwatch/Base.lproj/LaunchScreen.storyboard b/UIKit - Traditional/Stopwatch/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..865e932 --- /dev/null +++ b/UIKit - Traditional/Stopwatch/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UIKit - Traditional/Stopwatch/Cells/LapTableViewCell.swift b/UIKit - Traditional/Stopwatch/Cells/LapTableViewCell.swift new file mode 100644 index 0000000..ed99c0c --- /dev/null +++ b/UIKit - Traditional/Stopwatch/Cells/LapTableViewCell.swift @@ -0,0 +1,56 @@ +// +// LapTableViewCell.swift +// Stopwatch +// +// Created by Leonardo Bilia on 31/05/20. +// Copyright © 2020 Leonardo Bilia. All rights reserved. +// + +import UIKit + +class LapTableViewCell: UITableViewCell { + + private lazy var lapLabel = UILabel() + private lazy var timerLabel = UILabel() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setupComponents() + setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Methods + + func populate(lap: LapTime) { + lapLabel.text = lap.title + timerLabel.text = lap.time + } + + func setupComponents() { + lapLabel.translatesAutoresizingMaskIntoConstraints = false + timerLabel.translatesAutoresizingMaskIntoConstraints = false + } +} + +// MARK: - UI + +extension LapTableViewCell { + + private func setupUI() { + + contentView.addSubview(lapLabel) + contentView.addSubview(timerLabel) + + NSLayoutConstraint.activate([ + lapLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), + lapLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), + + timerLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), + timerLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16) + ]) + } +} diff --git a/UIKit - Traditional/Stopwatch/Components/SWButtom.swift b/UIKit - Traditional/Stopwatch/Components/SWButtom.swift new file mode 100644 index 0000000..dcb045e --- /dev/null +++ b/UIKit - Traditional/Stopwatch/Components/SWButtom.swift @@ -0,0 +1,28 @@ +// +// SWButtom.swift +// Stopwatch +// +// Created by Leonardo Bilia on 31/05/20. +// Copyright © 2020 Leonardo Bilia. All rights reserved. +// + +import UIKit + +class SWButton: UIButton { + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setup(title: String, color: UIColor) { + self.setTitle(title, for: .normal) + self.setTitleColor(UIColor.white, for: .normal) + self.layer.cornerRadius = 40 + self.layer.masksToBounds = true + self.backgroundColor = color + } +} diff --git a/UIKit - Traditional/Stopwatch/Components/SWLabel.swift b/UIKit - Traditional/Stopwatch/Components/SWLabel.swift new file mode 100644 index 0000000..fad08f3 --- /dev/null +++ b/UIKit - Traditional/Stopwatch/Components/SWLabel.swift @@ -0,0 +1,26 @@ +// +// SWLabel.swift +// Stopwatch +// +// Created by Leonardo Bilia on 31/05/20. +// Copyright © 2020 Leonardo Bilia. All rights reserved. +// + +import UIKit + +class SWLabel: UILabel { + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setup(title: String) { + self.text = title + self.font = UIFont.systemFont(ofSize: 75) + self.textAlignment = .center + } +} diff --git a/UIKit - Traditional/Stopwatch/Components/SWStackView.swift b/UIKit - Traditional/Stopwatch/Components/SWStackView.swift new file mode 100644 index 0000000..52f68e7 --- /dev/null +++ b/UIKit - Traditional/Stopwatch/Components/SWStackView.swift @@ -0,0 +1,26 @@ +// +// SWStackView.swift +// Stopwatch +// +// Created by Leonardo Bilia on 31/05/20. +// Copyright © 2020 Leonardo Bilia. All rights reserved. +// + +import UIKit + +class SWStackView: UIStackView { + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setup(spacing: CGFloat, distribution: UIStackView.Distribution) { + self.axis = .horizontal + self.spacing = spacing + self.distribution = distribution + } +} diff --git a/UIKit - Traditional/Stopwatch/Controllers/StopwatchViewController.swift b/UIKit - Traditional/Stopwatch/Controllers/StopwatchViewController.swift new file mode 100644 index 0000000..04a637f --- /dev/null +++ b/UIKit - Traditional/Stopwatch/Controllers/StopwatchViewController.swift @@ -0,0 +1,195 @@ +// +// StopwatchViewController.swift +// Stopwatch +// +// Created by Leonardo Bilia on 31/05/20. +// Copyright © 2020 Leonardo Bilia. All rights reserved. +// + +import UIKit + +class StopwatchViewController: UIViewController { + + private let reusableIdentifier = "SWCell" + private lazy var tableView = UITableView() + + private lazy var minuteLabel = SWLabel() + private lazy var secondLabel = SWLabel() + private lazy var millisecondLabel = SWLabel() + private lazy var primarySeparatorLabel = SWLabel() + private lazy var secondarySeparatorLabel = SWLabel() + + private lazy var lapButton = SWButton() + private lazy var resetButton = SWButton() + private lazy var startStopButton = SWButton() + + private lazy var labelStackView = SWStackView() + private lazy var buttonStackView = SWStackView() + + private lazy var lappedTimes = [LapTime]() + private lazy var counter = 0.001 + private lazy var timerOn = false + private var timer = Timer() + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + setupTableView() + setupComponents() + setupUI() + } + + // MARK: - Actions + + @objc private func timerHandler() { + counter += 0.001 + + let millisecond = counter * 1000 + let remaingMilliseconds = Int((Int(millisecond) % 1000) / 10) + let second = Int((Int(millisecond) / 1000) % 60) + let minute = Int((Int(millisecond) / 1000) / 60 % 60) + + DispatchQueue.main.async { + self.minuteLabel.text = String(format: "%02i", minute) + self.secondLabel.text = String(format: "%02i", second) + self.millisecondLabel.text = String(format: "%02i", remaingMilliseconds) + } + } + + @objc private func startStopHandler() { + if timerOn { + timer.invalidate() + } else { + let timer = Timer(timeInterval: 0.001, target: self, selector: #selector(timerHandler), userInfo: nil, repeats: true) + RunLoop.current.add(timer, forMode: .common) + timer.tolerance = 0.0001 + self.timer = timer + } + self.timerOn.toggle() + DispatchQueue.main.async { + self.startStopButton.setTitle(self.timerOn ? "Stop" : "Start", for: .normal) + self.startStopButton.backgroundColor = self.timerOn ? .systemRed : .systemGreen + } + } + + @objc private func lapHandler() { + guard let minute = minuteLabel.text, let second = secondLabel.text, let millisecond = millisecondLabel.text, timerOn else { return } + + let indexPath = IndexPath(row: lappedTimes.count, section: 0) + lappedTimes.append(LapTime(title: "Lap \(indexPath.row + 1)", time: "\(minute):\(second):\(millisecond)")) + tableView.insertRows(at: [indexPath], with: .automatic) + } + + @objc private func resetHandler() { + lappedTimes = [] + timerOn = false + counter = 0 + timer.invalidate() + + DispatchQueue.main.async { + self.minuteLabel.setup(title: "00") + self.secondLabel.setup(title: "00") + self.millisecondLabel.setup(title: "00") + self.startStopButton.setTitle("Start", for: .normal) + self.startStopButton.backgroundColor = .systemGreen + self.tableView.reloadData() + } + } + + // MARK: - Methods + + func setupTableView() { + tableView.delegate = self + tableView.dataSource = self + tableView.register(LapTableViewCell.self, forCellReuseIdentifier: reusableIdentifier) + tableView.estimatedRowHeight = 50 + tableView.rowHeight = 50 + } + + func setupComponents() { + minuteLabel.setup(title: "00") + secondLabel.setup(title: "00") + millisecondLabel.setup(title: "00") + primarySeparatorLabel.setup(title: ":") + secondarySeparatorLabel.setup(title: ":") + + labelStackView.setup(spacing: 0, distribution: .equalCentering) + buttonStackView.setup(spacing: 16, distribution: .fillEqually) + + lapButton.setup(title: "Lap", color: .secondaryLabel) + lapButton.addTarget(self, action: #selector(lapHandler), for: .touchUpInside) + + resetButton.setup(title: "Reset", color: .secondaryLabel) + resetButton.addTarget(self, action: #selector(resetHandler), for: .touchUpInside) + + startStopButton.setup(title: "Start", color: .systemGreen) + startStopButton.addTarget(self, action: #selector(startStopHandler), for: .touchUpInside) + } +} + +// MARK: - Table View Delegates and Data Sources + +extension StopwatchViewController: UITableViewDelegate, UITableViewDataSource { + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return lappedTimes.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: reusableIdentifier, for: indexPath) as! LapTableViewCell + cell.selectionStyle = .none + cell.populate(lap: lappedTimes[indexPath.row]) + return cell + } +} + + +// MARK: - UI + +extension StopwatchViewController { + + func setupUI() { + view.backgroundColor = .systemBackground + + view.addSubview(tableView) + view.addSubview(minuteLabel) + view.addSubview(buttonStackView) + buttonStackView.addArrangedSubview(lapButton) + buttonStackView.addArrangedSubview(resetButton) + buttonStackView.addArrangedSubview(startStopButton) + + view.addSubview(labelStackView) + labelStackView.addArrangedSubview(minuteLabel) + labelStackView.addArrangedSubview(primarySeparatorLabel) + labelStackView.addArrangedSubview(secondLabel) + labelStackView.addArrangedSubview(secondarySeparatorLabel) + labelStackView.addArrangedSubview(millisecondLabel) + + view.subviews.forEach({ element in + element.translatesAutoresizingMaskIntoConstraints = false + }) + + let width: CGFloat = (UIScreen.main.bounds.width / 3) - 28 + NSLayoutConstraint.activate([ + labelStackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + labelStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16), + labelStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16), + + minuteLabel.widthAnchor.constraint(equalToConstant: width), + secondLabel.widthAnchor.constraint(equalToConstant: width), + millisecondLabel.widthAnchor.constraint(equalToConstant: width), + + buttonStackView.topAnchor.constraint(equalTo: labelStackView.bottomAnchor), + buttonStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16), + buttonStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16), + buttonStackView.bottomAnchor.constraint(equalTo: view.centerYAnchor), + buttonStackView.heightAnchor.constraint(equalToConstant: 80), + + tableView.topAnchor.constraint(equalTo: buttonStackView.bottomAnchor, constant: 8), + tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor) + ]) + } +} diff --git a/UIKit - Traditional/Stopwatch/Info.plist b/UIKit - Traditional/Stopwatch/Info.plist new file mode 100644 index 0000000..35579ca --- /dev/null +++ b/UIKit - Traditional/Stopwatch/Info.plist @@ -0,0 +1,60 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UIRequiresFullScreen + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/UIKit - Traditional/Stopwatch/Model/LapTime.swift b/UIKit - Traditional/Stopwatch/Model/LapTime.swift new file mode 100644 index 0000000..b6c4c28 --- /dev/null +++ b/UIKit - Traditional/Stopwatch/Model/LapTime.swift @@ -0,0 +1,14 @@ +// +// LapTime.swift +// Stopwatch +// +// Created by Leonardo Bilia on 31/05/20. +// Copyright © 2020 Leonardo Bilia. All rights reserved. +// + +import Foundation + +struct LapTime: Decodable, Hashable { + var title: String + var time: String +} diff --git a/UIKit - Traditional/Stopwatch/System/AppDelegate.swift b/UIKit - Traditional/Stopwatch/System/AppDelegate.swift new file mode 100644 index 0000000..16345af --- /dev/null +++ b/UIKit - Traditional/Stopwatch/System/AppDelegate.swift @@ -0,0 +1,34 @@ +// +// AppDelegate.swift +// Stopwatch +// +// Created by Leonardo Bilia on 31/05/20. +// Copyright © 2020 Leonardo Bilia. All rights reserved. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + static var orientationLock = UIInterfaceOrientationMask.portrait + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + return true + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + + } + + func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask { + return AppDelegate.orientationLock + } +} + diff --git a/UIKit - Traditional/Stopwatch/System/SceneDelegate.swift b/UIKit - Traditional/Stopwatch/System/SceneDelegate.swift new file mode 100644 index 0000000..0a11842 --- /dev/null +++ b/UIKit - Traditional/Stopwatch/System/SceneDelegate.swift @@ -0,0 +1,43 @@ +// +// SceneDelegate.swift +// Stopwatch +// +// Created by Leonardo Bilia on 31/05/20. +// Copyright © 2020 Leonardo Bilia. All rights reserved. +// + +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + guard let windowScene = scene as? UIWindowScene else { return } + let window = UIWindow(windowScene: windowScene) + window.rootViewController = StopwatchViewController() + self.window = window + window.makeKeyAndVisible() + } + + func sceneDidDisconnect(_ scene: UIScene) { + + } + + func sceneDidBecomeActive(_ scene: UIScene) { + + } + + func sceneWillResignActive(_ scene: UIScene) { + + } + + func sceneWillEnterForeground(_ scene: UIScene) { + + } + + func sceneDidEnterBackground(_ scene: UIScene) { + + } +} +