diff --git a/Container/Container-SB/Container/Base.lproj/Main.storyboard b/Container/Container-SB/Container/Base.lproj/Main.storyboard index ed85341..e5bdbfe 100644 --- a/Container/Container-SB/Container/Base.lproj/Main.storyboard +++ b/Container/Container-SB/Container/Base.lproj/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -35,7 +35,7 @@ - + @@ -78,7 +78,7 @@ - + @@ -117,7 +117,7 @@ - + @@ -149,7 +149,6 @@ - diff --git a/DarkTheme/DarkTheme.xcodeproj/project.pbxproj b/DarkTheme/DarkTheme.xcodeproj/project.pbxproj new file mode 100644 index 0000000..6940263 --- /dev/null +++ b/DarkTheme/DarkTheme.xcodeproj/project.pbxproj @@ -0,0 +1,357 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 535DFF62241589FD00DF6476 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535DFF61241589FD00DF6476 /* AppDelegate.swift */; }; + 535DFF64241589FD00DF6476 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535DFF63241589FD00DF6476 /* SceneDelegate.swift */; }; + 535DFF6B241589FD00DF6476 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 535DFF69241589FD00DF6476 /* Main.storyboard */; }; + 535DFF6D24158A0100DF6476 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 535DFF6C24158A0100DF6476 /* Assets.xcassets */; }; + 535DFF7024158A0100DF6476 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 535DFF6E24158A0100DF6476 /* LaunchScreen.storyboard */; }; + 535DFF7824158B3E00DF6476 /* ThemeTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535DFF7724158B3E00DF6476 /* ThemeTableViewController.swift */; }; + 535DFF7A24158B8E00DF6476 /* TextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535DFF7924158B8E00DF6476 /* TextViewController.swift */; }; + 535DFF7C241597CA00DF6476 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535DFF7B241597CA00DF6476 /* Theme.swift */; }; + 535DFF7E2415996900DF6476 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535DFF7D2415996900DF6476 /* Settings.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 535DFF5E241589FD00DF6476 /* DarkTheme.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DarkTheme.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 535DFF61241589FD00DF6476 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 535DFF63241589FD00DF6476 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 535DFF6A241589FD00DF6476 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 535DFF6C24158A0100DF6476 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 535DFF6F24158A0100DF6476 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 535DFF7124158A0100DF6476 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 535DFF7724158B3E00DF6476 /* ThemeTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeTableViewController.swift; sourceTree = ""; }; + 535DFF7924158B8E00DF6476 /* TextViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewController.swift; sourceTree = ""; }; + 535DFF7B241597CA00DF6476 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; + 535DFF7D2415996900DF6476 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; + 535DFF7F2416A58B00DF6476 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 535DFF5B241589FD00DF6476 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 535DFF55241589FD00DF6476 = { + isa = PBXGroup; + children = ( + 535DFF7F2416A58B00DF6476 /* README.md */, + 535DFF60241589FD00DF6476 /* DarkTheme */, + 535DFF5F241589FD00DF6476 /* Products */, + ); + sourceTree = ""; + }; + 535DFF5F241589FD00DF6476 /* Products */ = { + isa = PBXGroup; + children = ( + 535DFF5E241589FD00DF6476 /* DarkTheme.app */, + ); + name = Products; + sourceTree = ""; + }; + 535DFF60241589FD00DF6476 /* DarkTheme */ = { + isa = PBXGroup; + children = ( + 535DFF7724158B3E00DF6476 /* ThemeTableViewController.swift */, + 535DFF7B241597CA00DF6476 /* Theme.swift */, + 535DFF7D2415996900DF6476 /* Settings.swift */, + 535DFF7924158B8E00DF6476 /* TextViewController.swift */, + 535DFF61241589FD00DF6476 /* AppDelegate.swift */, + 535DFF63241589FD00DF6476 /* SceneDelegate.swift */, + 535DFF69241589FD00DF6476 /* Main.storyboard */, + 535DFF6C24158A0100DF6476 /* Assets.xcassets */, + 535DFF6E24158A0100DF6476 /* LaunchScreen.storyboard */, + 535DFF7124158A0100DF6476 /* Info.plist */, + ); + path = DarkTheme; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 535DFF5D241589FD00DF6476 /* DarkTheme */ = { + isa = PBXNativeTarget; + buildConfigurationList = 535DFF7424158A0100DF6476 /* Build configuration list for PBXNativeTarget "DarkTheme" */; + buildPhases = ( + 535DFF5A241589FD00DF6476 /* Sources */, + 535DFF5B241589FD00DF6476 /* Frameworks */, + 535DFF5C241589FD00DF6476 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = DarkTheme; + productName = DarkTheme; + productReference = 535DFF5E241589FD00DF6476 /* DarkTheme.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 535DFF56241589FD00DF6476 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1140; + LastUpgradeCheck = 1140; + ORGANIZATIONNAME = "Keith Harrison"; + TargetAttributes = { + 535DFF5D241589FD00DF6476 = { + CreatedOnToolsVersion = 11.4; + }; + }; + }; + buildConfigurationList = 535DFF59241589FD00DF6476 /* Build configuration list for PBXProject "DarkTheme" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 535DFF55241589FD00DF6476; + productRefGroup = 535DFF5F241589FD00DF6476 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 535DFF5D241589FD00DF6476 /* DarkTheme */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 535DFF5C241589FD00DF6476 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 535DFF7024158A0100DF6476 /* LaunchScreen.storyboard in Resources */, + 535DFF6D24158A0100DF6476 /* Assets.xcassets in Resources */, + 535DFF6B241589FD00DF6476 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 535DFF5A241589FD00DF6476 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 535DFF7E2415996900DF6476 /* Settings.swift in Sources */, + 535DFF7824158B3E00DF6476 /* ThemeTableViewController.swift in Sources */, + 535DFF62241589FD00DF6476 /* AppDelegate.swift in Sources */, + 535DFF7C241597CA00DF6476 /* Theme.swift in Sources */, + 535DFF64241589FD00DF6476 /* SceneDelegate.swift in Sources */, + 535DFF7A24158B8E00DF6476 /* TextViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 535DFF69241589FD00DF6476 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 535DFF6A241589FD00DF6476 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 535DFF6E24158A0100DF6476 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 535DFF6F24158A0100DF6476 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 535DFF7224158A0100DF6476 /* 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.4; + 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; + }; + 535DFF7324158A0100DF6476 /* 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.4; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 535DFF7524158A0100DF6476 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = LCC2J94N44; + INFOPLIST_FILE = DarkTheme/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.useyourloaf.DarkTheme; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 535DFF7624158A0100DF6476 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = LCC2J94N44; + INFOPLIST_FILE = DarkTheme/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.useyourloaf.DarkTheme; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 535DFF59241589FD00DF6476 /* Build configuration list for PBXProject "DarkTheme" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 535DFF7224158A0100DF6476 /* Debug */, + 535DFF7324158A0100DF6476 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 535DFF7424158A0100DF6476 /* Build configuration list for PBXNativeTarget "DarkTheme" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 535DFF7524158A0100DF6476 /* Debug */, + 535DFF7624158A0100DF6476 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 535DFF56241589FD00DF6476 /* Project object */; +} diff --git a/DarkTheme/DarkTheme/AppDelegate.swift b/DarkTheme/DarkTheme/AppDelegate.swift new file mode 100644 index 0000000..2d32c08 --- /dev/null +++ b/DarkTheme/DarkTheme/AppDelegate.swift @@ -0,0 +1,34 @@ +// Created by Keith Harrison https://useyourloaf.com +// Copyright © 2020 Keith Harrison. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { +} diff --git a/DarkTheme/DarkTheme/Assets.xcassets/AppIcon.appiconset/Contents.json b/DarkTheme/DarkTheme/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..9221b9b --- /dev/null +++ b/DarkTheme/DarkTheme/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/DarkTheme/DarkTheme/Assets.xcassets/Contents.json b/DarkTheme/DarkTheme/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/DarkTheme/DarkTheme/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DarkTheme/DarkTheme/Assets.xcassets/Dynamic Colors/Contents.json b/DarkTheme/DarkTheme/Assets.xcassets/Dynamic Colors/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/DarkTheme/DarkTheme/Assets.xcassets/Dynamic Colors/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DarkTheme/DarkTheme/Assets.xcassets/Dynamic Colors/defaultBackground.colorset/Contents.json b/DarkTheme/DarkTheme/Assets.xcassets/Dynamic Colors/defaultBackground.colorset/Contents.json new file mode 100644 index 0000000..de36eae --- /dev/null +++ b/DarkTheme/DarkTheme/Assets.xcassets/Dynamic Colors/defaultBackground.colorset/Contents.json @@ -0,0 +1,78 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF0", + "green" : "0xF0", + "red" : "0xF0" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x33", + "green" : "0x33", + "red" : "0x33" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "contrast", + "value" : "high" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + }, + { + "appearance" : "contrast", + "value" : "high" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x00", + "green" : "0x00", + "red" : "0x00" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DarkTheme/DarkTheme/Assets.xcassets/Dynamic Colors/groupedBackground.colorset/Contents.json b/DarkTheme/DarkTheme/Assets.xcassets/Dynamic Colors/groupedBackground.colorset/Contents.json new file mode 100644 index 0000000..3d85e7f --- /dev/null +++ b/DarkTheme/DarkTheme/Assets.xcassets/Dynamic Colors/groupedBackground.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF7", + "green" : "0xF2", + "red" : "0xF2" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x00", + "green" : "0x00", + "red" : "0x00" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DarkTheme/DarkTheme/Base.lproj/LaunchScreen.storyboard b/DarkTheme/DarkTheme/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..865e932 --- /dev/null +++ b/DarkTheme/DarkTheme/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DarkTheme/DarkTheme/Base.lproj/Main.storyboard b/DarkTheme/DarkTheme/Base.lproj/Main.storyboard new file mode 100644 index 0000000..e93a726 --- /dev/null +++ b/DarkTheme/DarkTheme/Base.lproj/Main.storyboard @@ -0,0 +1,187 @@ + + + + + + + + + + + + + + + + + + + + + Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DarkTheme/DarkTheme/Info.plist b/DarkTheme/DarkTheme/Info.plist new file mode 100644 index 0000000..e7f6f58 --- /dev/null +++ b/DarkTheme/DarkTheme/Info.plist @@ -0,0 +1,74 @@ + + + + + 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 + UISceneStoryboardFile + Main + + + + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UIStatusBarTintParameters + + UINavigationBar + + Style + UIBarStyleDefault + Translucent + + + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/DarkTheme/DarkTheme/SceneDelegate.swift b/DarkTheme/DarkTheme/SceneDelegate.swift new file mode 100644 index 0000000..f5dc8a3 --- /dev/null +++ b/DarkTheme/DarkTheme/SceneDelegate.swift @@ -0,0 +1,39 @@ +// Created by Keith Harrison https://useyourloaf.com +// Copyright © 2020 Keith Harrison. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + var window: UIWindow? + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + let defaults = UserDefaults.standard + window?.overrideUserInterfaceStyle = defaults.theme.userInterfaceStyle + } +} diff --git a/DarkTheme/DarkTheme/Settings.swift b/DarkTheme/DarkTheme/Settings.swift new file mode 100644 index 0000000..6c54941 --- /dev/null +++ b/DarkTheme/DarkTheme/Settings.swift @@ -0,0 +1,42 @@ +// Created by Keith Harrison https://useyourloaf.com +// Copyright © 2020 Keith Harrison. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +import Foundation + +extension UserDefaults { + var theme: Theme { + get { + register(defaults: [#function: Theme.device.rawValue]) + return Theme(rawValue: integer(forKey: #function)) ?? .device + } + set { + set(newValue.rawValue, forKey: #function) + } + } +} diff --git a/DarkTheme/DarkTheme/TextViewController.swift b/DarkTheme/DarkTheme/TextViewController.swift new file mode 100644 index 0000000..d42d839 --- /dev/null +++ b/DarkTheme/DarkTheme/TextViewController.swift @@ -0,0 +1,48 @@ +// Created by Keith Harrison https://useyourloaf.com +// Copyright © 2020 Keith Harrison. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +import UIKit + +final class TextViewController: UIViewController { + var text: String? { + didSet { + textView?.text = text + } + } + + @IBOutlet private var textView: UITextView? + + override func viewDidLoad() { + super.viewDidLoad() + text = """ + “Here's to the crazy ones. The misfits. The rebels. The troublemakers. The round pegs in the square holes. The ones who see things differently. They're not fond of rules. And they have no respect for the status quo. You can quote them, disagree with them, glorify or vilify them. About the only thing you can't do is ignore them. Because they change things. They push the human race forward. And while some may see them as the crazy ones, we see genius. Because the people who are crazy enough to think they can change the world, are the ones who do.” + ― Rob Siltanen + """ + } +} diff --git a/DarkTheme/DarkTheme/Theme.swift b/DarkTheme/DarkTheme/Theme.swift new file mode 100644 index 0000000..0aaa910 --- /dev/null +++ b/DarkTheme/DarkTheme/Theme.swift @@ -0,0 +1,49 @@ +// Created by Keith Harrison https://useyourloaf.com +// Copyright © 2020 Keith Harrison. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +import UIKit + +enum Theme: Int { + case device + case light + case dark +} + +extension Theme { + var userInterfaceStyle: UIUserInterfaceStyle { + switch self { + case .device: + return .unspecified + case .light: + return .light + case .dark: + return .dark + } + } +} diff --git a/DarkTheme/DarkTheme/ThemeTableViewController.swift b/DarkTheme/DarkTheme/ThemeTableViewController.swift new file mode 100644 index 0000000..438ad54 --- /dev/null +++ b/DarkTheme/DarkTheme/ThemeTableViewController.swift @@ -0,0 +1,67 @@ +// Created by Keith Harrison https://useyourloaf.com +// Copyright © 2020 Keith Harrison. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +import UIKit + +final class ThemeTableViewController: UITableViewController { + var defaults = UserDefaults.standard + private var theme: Theme { + get { + return defaults.theme + } + set { + defaults.theme = newValue + configureStyle(for: newValue) + } + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + configureCell(for: theme, checked: true) + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if indexPath.row != theme.rawValue { + configureCell(for: theme, checked: false) + theme = Theme(rawValue: indexPath.row) ?? .device + configureCell(for: theme, checked: true) + } + + tableView.deselectRow(at: indexPath, animated: true) + } + + private func configureCell(for theme: Theme, checked: Bool) { + let cell = tableView.cellForRow(at: IndexPath(row: theme.rawValue, section: 0)) + cell?.accessoryType = checked ? .checkmark : .none + } + + private func configureStyle(for theme: Theme) { + view.window?.overrideUserInterfaceStyle = theme.userInterfaceStyle + } +} diff --git a/DarkTheme/README.md b/DarkTheme/README.md new file mode 100644 index 0000000..180e52d --- /dev/null +++ b/DarkTheme/README.md @@ -0,0 +1,5 @@ +# Overriding Dark Mode + +An example of using `overrideUserInterfaceStyle` (iOS 13) to control the appearance of an App. See the following post for more details: + ++ [Overriding Dark Mode](https://useyourloaf.com/blog/overriding-dark-mode/) diff --git a/DynamicWebKit/DynamicWebKit.xcodeproj/project.pbxproj b/DynamicWebKit/DynamicWebKit.xcodeproj/project.pbxproj index f0255cc..995fc45 100644 --- a/DynamicWebKit/DynamicWebKit.xcodeproj/project.pbxproj +++ b/DynamicWebKit/DynamicWebKit.xcodeproj/project.pbxproj @@ -8,25 +8,33 @@ /* Begin PBXBuildFile section */ 53A2B53B1F94DBE500095FAD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A2B53A1F94DBE500095FAD /* AppDelegate.swift */; }; - 53A2B53D1F94DBE500095FAD /* HTMLViewControler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A2B53C1F94DBE500095FAD /* HTMLViewControler.swift */; }; + 53A2B53D1F94DBE500095FAD /* WebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A2B53C1F94DBE500095FAD /* WebViewController.swift */; }; 53A2B5401F94DBE500095FAD /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 53A2B53E1F94DBE500095FAD /* Main.storyboard */; }; 53A2B5421F94DBE500095FAD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 53A2B5411F94DBE500095FAD /* Assets.xcassets */; }; 53A2B5451F94DBE500095FAD /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 53A2B5431F94DBE500095FAD /* LaunchScreen.storyboard */; }; - 53A2B54D1F94DC9700095FAD /* readme.html in Resources */ = {isa = PBXBuildFile; fileRef = 53A2B54C1F94DC9700095FAD /* readme.html */; }; + 53A2B54D1F94DC9700095FAD /* default.html in Resources */ = {isa = PBXBuildFile; fileRef = 53A2B54C1F94DC9700095FAD /* default.html */; }; 53A2B54F1F94DD8200095FAD /* stylesheet.css in Resources */ = {isa = PBXBuildFile; fileRef = 53A2B54E1F94DD8200095FAD /* stylesheet.css */; }; + 53E34E032403D85700EE575E /* 001-dark.png in Resources */ = {isa = PBXBuildFile; fileRef = 53E34E022403D67600EE575E /* 001-dark.png */; }; + 53E34E042403D85A00EE575E /* 001-dark@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 53E34DFF2403D67600EE575E /* 001-dark@2x.png */; }; + 53E34E052403D85E00EE575E /* 001.png in Resources */ = {isa = PBXBuildFile; fileRef = 53E34E012403D67600EE575E /* 001.png */; }; + 53E34E062403D86100EE575E /* 001@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 53E34E002403D67600EE575E /* 001@2x.png */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 53A2B5371F94DBE500095FAD /* DynamicWebKit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DynamicWebKit.app; sourceTree = BUILT_PRODUCTS_DIR; }; 53A2B53A1F94DBE500095FAD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 53A2B53C1F94DBE500095FAD /* HTMLViewControler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTMLViewControler.swift; sourceTree = ""; }; + 53A2B53C1F94DBE500095FAD /* WebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewController.swift; sourceTree = ""; }; 53A2B53F1F94DBE500095FAD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 53A2B5411F94DBE500095FAD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 53A2B5441F94DBE500095FAD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 53A2B5461F94DBE500095FAD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 53A2B54C1F94DC9700095FAD /* readme.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = readme.html; sourceTree = ""; }; + 53A2B54C1F94DC9700095FAD /* default.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = default.html; sourceTree = ""; }; 53A2B54E1F94DD8200095FAD /* stylesheet.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = stylesheet.css; sourceTree = ""; }; 53A2B5501F94F14800095FAD /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + 53E34DFF2403D67600EE575E /* 001-dark@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "001-dark@2x.png"; sourceTree = ""; }; + 53E34E002403D67600EE575E /* 001@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "001@2x.png"; sourceTree = ""; }; + 53E34E012403D67600EE575E /* 001.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = 001.png; sourceTree = ""; }; + 53E34E022403D67600EE575E /* 001-dark.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "001-dark.png"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -61,9 +69,8 @@ isa = PBXGroup; children = ( 53A2B53A1F94DBE500095FAD /* AppDelegate.swift */, - 53A2B53C1F94DBE500095FAD /* HTMLViewControler.swift */, - 53A2B54C1F94DC9700095FAD /* readme.html */, - 53A2B54E1F94DD8200095FAD /* stylesheet.css */, + 53A2B53C1F94DBE500095FAD /* WebViewController.swift */, + 53E34DFE2403D58400EE575E /* HTML */, 53A2B53E1F94DBE500095FAD /* Main.storyboard */, 53A2B5411F94DBE500095FAD /* Assets.xcassets */, 53A2B5431F94DBE500095FAD /* LaunchScreen.storyboard */, @@ -72,6 +79,19 @@ path = DynamicWebKit; sourceTree = ""; }; + 53E34DFE2403D58400EE575E /* HTML */ = { + isa = PBXGroup; + children = ( + 53A2B54C1F94DC9700095FAD /* default.html */, + 53E34E022403D67600EE575E /* 001-dark.png */, + 53E34DFF2403D67600EE575E /* 001-dark@2x.png */, + 53E34E012403D67600EE575E /* 001.png */, + 53E34E002403D67600EE575E /* 001@2x.png */, + 53A2B54E1F94DD8200095FAD /* stylesheet.css */, + ); + path = HTML; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -132,10 +152,14 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 53A2B54D1F94DC9700095FAD /* readme.html in Resources */, + 53E34E042403D85A00EE575E /* 001-dark@2x.png in Resources */, + 53A2B54D1F94DC9700095FAD /* default.html in Resources */, 53A2B54F1F94DD8200095FAD /* stylesheet.css in Resources */, + 53E34E052403D85E00EE575E /* 001.png in Resources */, 53A2B5451F94DBE500095FAD /* LaunchScreen.storyboard in Resources */, + 53E34E032403D85700EE575E /* 001-dark.png in Resources */, 53A2B5421F94DBE500095FAD /* Assets.xcassets in Resources */, + 53E34E062403D86100EE575E /* 001@2x.png in Resources */, 53A2B5401F94DBE500095FAD /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -147,7 +171,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 53A2B53D1F94DBE500095FAD /* HTMLViewControler.swift in Sources */, + 53A2B53D1F94DBE500095FAD /* WebViewController.swift in Sources */, 53A2B53B1F94DBE500095FAD /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/DynamicWebKit/DynamicWebKit/Base.lproj/Main.storyboard b/DynamicWebKit/DynamicWebKit/Base.lproj/Main.storyboard index afb9d6c..c0a6762 100644 --- a/DynamicWebKit/DynamicWebKit/Base.lproj/Main.storyboard +++ b/DynamicWebKit/DynamicWebKit/Base.lproj/Main.storyboard @@ -1,29 +1,28 @@ - - - - + + - + + - + - + - + - + @@ -31,7 +30,7 @@ - + diff --git a/DynamicWebKit/DynamicWebKit/HTML/001-dark.png b/DynamicWebKit/DynamicWebKit/HTML/001-dark.png new file mode 100644 index 0000000..98e7b46 Binary files /dev/null and b/DynamicWebKit/DynamicWebKit/HTML/001-dark.png differ diff --git a/DynamicWebKit/DynamicWebKit/HTML/001-dark@2x.png b/DynamicWebKit/DynamicWebKit/HTML/001-dark@2x.png new file mode 100644 index 0000000..867a616 Binary files /dev/null and b/DynamicWebKit/DynamicWebKit/HTML/001-dark@2x.png differ diff --git a/DynamicWebKit/DynamicWebKit/HTML/001.png b/DynamicWebKit/DynamicWebKit/HTML/001.png new file mode 100644 index 0000000..1d96a9a Binary files /dev/null and b/DynamicWebKit/DynamicWebKit/HTML/001.png differ diff --git a/DynamicWebKit/DynamicWebKit/HTML/001@2x.png b/DynamicWebKit/DynamicWebKit/HTML/001@2x.png new file mode 100644 index 0000000..15688c5 Binary files /dev/null and b/DynamicWebKit/DynamicWebKit/HTML/001@2x.png differ diff --git a/DynamicWebKit/DynamicWebKit/HTML/default.html b/DynamicWebKit/DynamicWebKit/HTML/default.html new file mode 100644 index 0000000..96c14f7 --- /dev/null +++ b/DynamicWebKit/DynamicWebKit/HTML/default.html @@ -0,0 +1,21 @@ + + + + + + +User Guide + + +

Dynamic Type With WebKit

+

Getting Started

+

An example of using dynamic type fonts with HTML content displayed in a WKWebView. Changing the text size in Settings should also change the text in this web view.

+

Handling Images

+

An example of an image with a dark mode variation

+ + +Xcode source editor + + + + diff --git a/DynamicWebKit/DynamicWebKit/HTML/stylesheet.css b/DynamicWebKit/DynamicWebKit/HTML/stylesheet.css new file mode 100644 index 0000000..00998a7 --- /dev/null +++ b/DynamicWebKit/DynamicWebKit/HTML/stylesheet.css @@ -0,0 +1,40 @@ +:root { + color-scheme: light dark; + --title-color: red; + --subhead-color: green; + --link-color: blue; +} + +@media screen and (prefers-color-scheme: dark) { + :root { + --title-color: #ff8080; + --subhead-color: #80ff80; + --link-color: #93d5ff; + } +} + +body { + font: -apple-system-body; +} + +h1 { + font: -apple-system-headline; + color: var(--title-color); +} + +h2 { + font: -apple-system-subheadline; + color: var(--subhead-color); +} + +footer { + font: -apple-system-footnote; +} + +a { + color: var(--link-color); +} + +img { + max-width: 100%; +} diff --git a/DynamicWebKit/DynamicWebKit/HTMLViewControler.swift b/DynamicWebKit/DynamicWebKit/WebViewController.swift similarity index 65% rename from DynamicWebKit/DynamicWebKit/HTMLViewControler.swift rename to DynamicWebKit/DynamicWebKit/WebViewController.swift index 779671d..90b5382 100644 --- a/DynamicWebKit/DynamicWebKit/HTMLViewControler.swift +++ b/DynamicWebKit/DynamicWebKit/WebViewController.swift @@ -1,5 +1,5 @@ // Created by Keith Harrison https://useyourloaf.com -// Copyright (c) 2017 Keith Harrison. All rights reserved. +// Copyright (c) 2017-2020 Keith Harrison. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: @@ -30,43 +30,34 @@ import UIKit import WebKit -final class HTMLViewControler: UIViewController { - private lazy var webview: WKWebView = { +final class WebViewController: UIViewController { + var html: String = "default" { + didSet { + loadHTML(html) + } + } + + private lazy var webView: WKWebView = { let preferences = WKPreferences() preferences.javaScriptEnabled = false let configuration = WKWebViewConfiguration() configuration.preferences = preferences - let webview = WKWebView(frame: .zero, configuration: configuration) - webview.translatesAutoresizingMaskIntoConstraints = false - return webview + return WKWebView(frame: .zero, configuration: configuration) }() - override func viewDidLoad() { - super.viewDidLoad() - setupViews() - loadHTML("readme.html") - + override func loadView() { + view = webView + loadHTML(html) NotificationCenter.default.addObserver(self, selector: #selector(contentSizeDidChange(_:)), name: UIContentSizeCategory.didChangeNotification, object: nil) } - private func setupViews() { - view.addSubview(webview) - NSLayoutConstraint.activate([ - webview.leadingAnchor.constraint(equalTo: view.leadingAnchor), - webview.trailingAnchor.constraint(equalTo: view.trailingAnchor), - webview.topAnchor.constraint(equalTo: view.topAnchor), - webview.bottomAnchor.constraint(equalTo: view.bottomAnchor) - ]) + @objc private func contentSizeDidChange(_ notification: Notification) { + webView.reload() } - private func loadHTML(_ file: String) { - if let baseURL = Bundle.main.resourceURL { - let fileURL = baseURL.appendingPathComponent(file) - webview.loadFileURL(fileURL, allowingReadAccessTo: fileURL) + private func loadHTML(_ name: String) { + if let url = Bundle.main.url(forResource: name, withExtension: "html") { + webView.loadFileURL(url, allowingReadAccessTo: url) } } - - @objc private func contentSizeDidChange(_ notification: Notification) { - webview.reload() - } } diff --git a/DynamicWebKit/DynamicWebKit/readme.html b/DynamicWebKit/DynamicWebKit/readme.html deleted file mode 100644 index 5055016..0000000 --- a/DynamicWebKit/DynamicWebKit/readme.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - -

Dynamic Type With WebKit

-

Getting Started

-

An example of using dynamic type fonts with HTML content displayed in a WKWebView.

-

Changing the text size in Settings should also change the text in this web view.

- - - - diff --git a/DynamicWebKit/DynamicWebKit/stylesheet.css b/DynamicWebKit/DynamicWebKit/stylesheet.css deleted file mode 100644 index 59ff130..0000000 --- a/DynamicWebKit/DynamicWebKit/stylesheet.css +++ /dev/null @@ -1,18 +0,0 @@ -body { - font: -apple-system-body; -} - -h1 { - font: -apple-system-headline; - color: red; -} - -h2 { - font: -apple-system-subheadline; - color: blue; -} - -.footnote { - font: -apple-system-footnote; - color: green; -} diff --git a/DynamicWebKit/README.md b/DynamicWebKit/README.md index 188df97..3f59978 100644 --- a/DynamicWebKit/README.md +++ b/DynamicWebKit/README.md @@ -2,8 +2,13 @@ An example of how to use the Apple system fonts in a CSS stylesheet so static HTML content in a web view respects the user's choice of content size. +## Adding Support For Dark Mode + +I've updated this project to support dark mode (requires iOS 13). + ## Further Details -For further details see the following blog post: +For further details see the following blog posts: + [Using Dynamic Type With Web Views](https://useyourloaf.com/blog/using-dynamic-type-with-web-views/) ++ [Supporting Dark Mode In WkWebView](https://useyourloaf.com/blog/supporting-dark-mode-in-wkwebview/) diff --git a/ScaledFont/README.md b/ScaledFont/README.md index 616776f..d54fccd 100644 --- a/ScaledFont/README.md +++ b/ScaledFont/README.md @@ -2,11 +2,11 @@ ## Using Dynamic Type With Custom Fonts -An example of using custom fonts with dynamic type making use of the `UIFontMetrics` class introduced with iOS 11. It provides examples using the Noteworthy font which is built-in to iOS and the NotoSerif font which was downloaded from google fonts: +An example of using custom fonts with dynamic type making use of the `UIFontMetrics` class introduced with iOS 11. It provides examples using the Noteworthy font which is built-in to iOS and the Noto Serif font which was downloaded from google fonts: -+ [Noto Sans](https://fonts.google.com/specimen/Noto+Serif?selection.family=Noto+Serif) ++ [Noto Serif](https://fonts.google.com/specimen/Noto+Serif?selection.family=Noto+Serif) -*Refer to LICENSE.txt if you plan on using NotoSans in a shipping application.* +*Refer to LICENSE.txt if you plan on using Noto Serif in a shipping application.* **This project requires iOS 11** diff --git a/Validate/README.md b/Validate/README.md new file mode 100644 index 0000000..4329027 --- /dev/null +++ b/Validate/README.md @@ -0,0 +1,11 @@ +# Getting Started with Combine + +A gentle introduction to using Combine to validate user input. + +See the following blog post for more details: + ++ [Getting Started with Combine](https://useyourloaf.com/blog/getting-started-with-combine/) + +For details on using Xcode previews with UIKit view controllers see: + ++ [Xcode Previews for View Controllers](https:/useyourloaf.com/blog/xcode-previews-for-view-controllers/) diff --git a/Validate/Validate.xcodeproj/project.pbxproj b/Validate/Validate.xcodeproj/project.pbxproj new file mode 100644 index 0000000..f2273c2 --- /dev/null +++ b/Validate/Validate.xcodeproj/project.pbxproj @@ -0,0 +1,357 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 532A2F1D2425823D00A0005F /* String+Blank.swift in Sources */ = {isa = PBXBuildFile; fileRef = 532A2F1C2425823D00A0005F /* String+Blank.swift */; }; + 5371387A243202D6006CB01D /* TermsViewControllerPreviews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53713879243202D6006CB01D /* TermsViewControllerPreviews.swift */; }; + 5385DF762427C259001E63E0 /* AdaptiveScroll.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5385DF752427C259001E63E0 /* AdaptiveScroll.swift */; }; + 539CD53024250D75000E2699 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 539CD52F24250D75000E2699 /* AppDelegate.swift */; }; + 539CD53224250D75000E2699 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 539CD53124250D75000E2699 /* SceneDelegate.swift */; }; + 539CD53724250D75000E2699 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 539CD53524250D75000E2699 /* Main.storyboard */; }; + 539CD53924250D76000E2699 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 539CD53824250D76000E2699 /* Assets.xcassets */; }; + 539CD53C24250D76000E2699 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 539CD53A24250D76000E2699 /* LaunchScreen.storyboard */; }; + 539CD5442425109F000E2699 /* TermsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 539CD5432425109F000E2699 /* TermsViewController.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 532A2F1C2425823D00A0005F /* String+Blank.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Blank.swift"; sourceTree = ""; }; + 53713879243202D6006CB01D /* TermsViewControllerPreviews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsViewControllerPreviews.swift; sourceTree = ""; }; + 5385DF752427C259001E63E0 /* AdaptiveScroll.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveScroll.swift; sourceTree = ""; }; + 539CD52C24250D75000E2699 /* Validate.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Validate.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 539CD52F24250D75000E2699 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 539CD53124250D75000E2699 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 539CD53624250D75000E2699 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 539CD53824250D76000E2699 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 539CD53B24250D76000E2699 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 539CD53D24250D76000E2699 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 539CD5432425109F000E2699 /* TermsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsViewController.swift; sourceTree = ""; }; + 53D7D6D52429223700ACD670 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 539CD52924250D75000E2699 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 539CD52324250D75000E2699 = { + isa = PBXGroup; + children = ( + 53D7D6D52429223700ACD670 /* README.md */, + 539CD52E24250D75000E2699 /* Validate */, + 539CD52D24250D75000E2699 /* Products */, + ); + sourceTree = ""; + }; + 539CD52D24250D75000E2699 /* Products */ = { + isa = PBXGroup; + children = ( + 539CD52C24250D75000E2699 /* Validate.app */, + ); + name = Products; + sourceTree = ""; + }; + 539CD52E24250D75000E2699 /* Validate */ = { + isa = PBXGroup; + children = ( + 539CD5432425109F000E2699 /* TermsViewController.swift */, + 53713879243202D6006CB01D /* TermsViewControllerPreviews.swift */, + 5385DF752427C259001E63E0 /* AdaptiveScroll.swift */, + 532A2F1C2425823D00A0005F /* String+Blank.swift */, + 539CD52F24250D75000E2699 /* AppDelegate.swift */, + 539CD53124250D75000E2699 /* SceneDelegate.swift */, + 539CD53524250D75000E2699 /* Main.storyboard */, + 539CD53824250D76000E2699 /* Assets.xcassets */, + 539CD53A24250D76000E2699 /* LaunchScreen.storyboard */, + 539CD53D24250D76000E2699 /* Info.plist */, + ); + path = Validate; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 539CD52B24250D75000E2699 /* Validate */ = { + isa = PBXNativeTarget; + buildConfigurationList = 539CD54024250D76000E2699 /* Build configuration list for PBXNativeTarget "Validate" */; + buildPhases = ( + 539CD52824250D75000E2699 /* Sources */, + 539CD52924250D75000E2699 /* Frameworks */, + 539CD52A24250D75000E2699 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Validate; + productName = Validate; + productReference = 539CD52C24250D75000E2699 /* Validate.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 539CD52424250D75000E2699 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1140; + LastUpgradeCheck = 1140; + ORGANIZATIONNAME = "Keith Harrison"; + TargetAttributes = { + 539CD52B24250D75000E2699 = { + CreatedOnToolsVersion = 11.4; + }; + }; + }; + buildConfigurationList = 539CD52724250D75000E2699 /* Build configuration list for PBXProject "Validate" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 539CD52324250D75000E2699; + productRefGroup = 539CD52D24250D75000E2699 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 539CD52B24250D75000E2699 /* Validate */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 539CD52A24250D75000E2699 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 539CD53C24250D76000E2699 /* LaunchScreen.storyboard in Resources */, + 539CD53924250D76000E2699 /* Assets.xcassets in Resources */, + 539CD53724250D75000E2699 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 539CD52824250D75000E2699 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 539CD53024250D75000E2699 /* AppDelegate.swift in Sources */, + 5371387A243202D6006CB01D /* TermsViewControllerPreviews.swift in Sources */, + 5385DF762427C259001E63E0 /* AdaptiveScroll.swift in Sources */, + 539CD5442425109F000E2699 /* TermsViewController.swift in Sources */, + 539CD53224250D75000E2699 /* SceneDelegate.swift in Sources */, + 532A2F1D2425823D00A0005F /* String+Blank.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 539CD53524250D75000E2699 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 539CD53624250D75000E2699 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 539CD53A24250D76000E2699 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 539CD53B24250D76000E2699 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 539CD53E24250D76000E2699 /* 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.4; + 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; + }; + 539CD53F24250D76000E2699 /* 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.4; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 539CD54124250D76000E2699 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = LCC2J94N44; + INFOPLIST_FILE = Validate/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.useyourloaf.Validate; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 539CD54224250D76000E2699 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = LCC2J94N44; + INFOPLIST_FILE = Validate/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.useyourloaf.Validate; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 539CD52724250D75000E2699 /* Build configuration list for PBXProject "Validate" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 539CD53E24250D76000E2699 /* Debug */, + 539CD53F24250D76000E2699 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 539CD54024250D76000E2699 /* Build configuration list for PBXNativeTarget "Validate" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 539CD54124250D76000E2699 /* Debug */, + 539CD54224250D76000E2699 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 539CD52424250D75000E2699 /* Project object */; +} diff --git a/Validate/Validate/AdaptiveScroll.swift b/Validate/Validate/AdaptiveScroll.swift new file mode 100644 index 0000000..5f43115 --- /dev/null +++ b/Validate/Validate/AdaptiveScroll.swift @@ -0,0 +1,67 @@ +// Created by Keith Harrison https://useyourloaf.com +// Copyright © 2020 Keith Harrison. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +import UIKit + +final class AdaptiveScrollView: UIScrollView { + override init(frame: CGRect) { + super.init(frame: frame) + setup() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setup() + } + + private func setup() { + NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidShow(_:)), name: UIResponder.keyboardDidShowNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil) + } + + @objc private func keyboardDidShow(_ notification: Notification) { + guard let userInfo = notification.userInfo, + let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { + return + } + + let keyboardSize = keyboardFrame.cgRectValue.size + let contentInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: keyboardSize.height, right: 0.0) + adjustContentInsets(contentInsets) + } + + @objc private func keyboardWillHide(_ notification: Notification) { + adjustContentInsets(.zero) + } + + private func adjustContentInsets(_ contentInsets: UIEdgeInsets) { + contentInset = contentInsets + scrollIndicatorInsets = contentInsets + } +} diff --git a/Validate/Validate/AppDelegate.swift b/Validate/Validate/AppDelegate.swift new file mode 100644 index 0000000..2d32c08 --- /dev/null +++ b/Validate/Validate/AppDelegate.swift @@ -0,0 +1,34 @@ +// Created by Keith Harrison https://useyourloaf.com +// Copyright © 2020 Keith Harrison. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { +} diff --git a/Validate/Validate/Assets.xcassets/AppIcon.appiconset/Contents.json b/Validate/Validate/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..9221b9b --- /dev/null +++ b/Validate/Validate/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/Validate/Validate/Assets.xcassets/Contents.json b/Validate/Validate/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Validate/Validate/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Validate/Validate/Assets.xcassets/disabledButton.imageset/Contents.json b/Validate/Validate/Assets.xcassets/disabledButton.imageset/Contents.json new file mode 100644 index 0000000..bf86622 --- /dev/null +++ b/Validate/Validate/Assets.xcassets/disabledButton.imageset/Contents.json @@ -0,0 +1,29 @@ +{ + "images" : [ + { + "filename" : "grey.pdf", + "idiom" : "universal", + "resizing" : { + "cap-insets" : { + "bottom" : 10, + "left" : 10, + "right" : 10, + "top" : 10 + }, + "center" : { + "height" : 1, + "mode" : "tile", + "width" : 1 + }, + "mode" : "9-part" + } + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Validate/Validate/Assets.xcassets/disabledButton.imageset/grey.pdf b/Validate/Validate/Assets.xcassets/disabledButton.imageset/grey.pdf new file mode 100644 index 0000000..759973b Binary files /dev/null and b/Validate/Validate/Assets.xcassets/disabledButton.imageset/grey.pdf differ diff --git a/Validate/Validate/Assets.xcassets/selectedSubmit.imageset/Contents.json b/Validate/Validate/Assets.xcassets/selectedSubmit.imageset/Contents.json new file mode 100644 index 0000000..a9f61c7 --- /dev/null +++ b/Validate/Validate/Assets.xcassets/selectedSubmit.imageset/Contents.json @@ -0,0 +1,29 @@ +{ + "images" : [ + { + "filename" : "greensel.pdf", + "idiom" : "universal", + "resizing" : { + "cap-insets" : { + "bottom" : 10, + "left" : 10, + "right" : 10, + "top" : 10 + }, + "center" : { + "height" : 1, + "mode" : "tile", + "width" : 1 + }, + "mode" : "9-part" + } + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Validate/Validate/Assets.xcassets/selectedSubmit.imageset/greensel.pdf b/Validate/Validate/Assets.xcassets/selectedSubmit.imageset/greensel.pdf new file mode 100644 index 0000000..7b4ae44 Binary files /dev/null and b/Validate/Validate/Assets.xcassets/selectedSubmit.imageset/greensel.pdf differ diff --git a/Validate/Validate/Assets.xcassets/submitButton.imageset/Contents.json b/Validate/Validate/Assets.xcassets/submitButton.imageset/Contents.json new file mode 100644 index 0000000..fec438a --- /dev/null +++ b/Validate/Validate/Assets.xcassets/submitButton.imageset/Contents.json @@ -0,0 +1,29 @@ +{ + "images" : [ + { + "filename" : "green.pdf", + "idiom" : "universal", + "resizing" : { + "cap-insets" : { + "bottom" : 12, + "left" : 10, + "right" : 10, + "top" : 12 + }, + "center" : { + "height" : 1, + "mode" : "tile", + "width" : 1 + }, + "mode" : "9-part" + } + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Validate/Validate/Assets.xcassets/submitButton.imageset/green.pdf b/Validate/Validate/Assets.xcassets/submitButton.imageset/green.pdf new file mode 100644 index 0000000..b3fa12c Binary files /dev/null and b/Validate/Validate/Assets.xcassets/submitButton.imageset/green.pdf differ diff --git a/Validate/Validate/Base.lproj/LaunchScreen.storyboard b/Validate/Validate/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..865e932 --- /dev/null +++ b/Validate/Validate/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Validate/Validate/Base.lproj/Main.storyboard b/Validate/Validate/Base.lproj/Main.storyboard new file mode 100644 index 0000000..9b9f35e --- /dev/null +++ b/Validate/Validate/Base.lproj/Main.storyboard @@ -0,0 +1,212 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + You must accept these terms and conditions before continuing: + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus nec posuere nulla, sit amet fermentum tellus. Pellentesque bibendum eros sed mi ultricies hendrerit quis id mi. Curabitur quis sodales eros. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Validate/Validate/Info.plist b/Validate/Validate/Info.plist new file mode 100644 index 0000000..2a3483c --- /dev/null +++ b/Validate/Validate/Info.plist @@ -0,0 +1,64 @@ + + + + + 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 + UISceneStoryboardFile + Main + + + + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Validate/Validate/SceneDelegate.swift b/Validate/Validate/SceneDelegate.swift new file mode 100644 index 0000000..d6c5aaa --- /dev/null +++ b/Validate/Validate/SceneDelegate.swift @@ -0,0 +1,34 @@ +// Created by Keith Harrison https://useyourloaf.com +// Copyright © 2020 Keith Harrison. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +import UIKit + +final class SceneDelegate: UIResponder, UIWindowSceneDelegate { + var window: UIWindow? +} diff --git a/Validate/Validate/String+Blank.swift b/Validate/Validate/String+Blank.swift new file mode 100644 index 0000000..22fb8f3 --- /dev/null +++ b/Validate/Validate/String+Blank.swift @@ -0,0 +1,46 @@ +// Created by Keith Harrison https://useyourloaf.com +// Copyright © 2020 Keith Harrison. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +import Foundation + +extension String { + /// A Boolean value indicating whether a String is blank. + /// The string is blank if it is empty or only contains whitespace. + var isBlank: Bool { + return allSatisfy { $0.isWhitespace } + } +} + +extension Optional where Wrapped == String { + /// A Boolean value indicating whether an Optional String is blank. + /// The optional string is blank if it is nil, empty or only contains whitespace. + var isBlank: Bool { + return self?.isBlank ?? true + } +} diff --git a/Validate/Validate/TermsViewController.swift b/Validate/Validate/TermsViewController.swift new file mode 100644 index 0000000..3ca2409 --- /dev/null +++ b/Validate/Validate/TermsViewController.swift @@ -0,0 +1,115 @@ +// Created by Keith Harrison https://useyourloaf.com +// Copyright © 2020 Keith Harrison. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +import Combine +import UIKit + +final class TermsViewController: UIViewController { + @IBOutlet private var termsSwitch: UISwitch! + @IBOutlet private var privacySwitch: UISwitch! + @IBOutlet private var nameField: UITextField! + @IBOutlet private var submitButton: UIButton! + + @Published private var acceptedTerms: Bool = false + @Published private var acceptedPrivacy: Bool = false + @Published private var name: String = "" + + private var stream: AnyCancellable? + + override func viewDidLoad() { + super.viewDidLoad() + stream = validToSubmit + .receive(on: RunLoop.main) + .assign(to: \.isEnabled, on: submitButton) + } + + @IBAction private func acceptTerms(_ sender: UISwitch) { + acceptedTerms = sender.isOn + } + + @IBAction private func acceptPrivacy(_ sender: UISwitch) { + acceptedPrivacy = sender.isOn + } + + @IBAction private func nameChanged(_ sender: UITextField) { + name = sender.text ?? "" + } + + @IBAction private func submitAction(_ sender: UIButton) { + print("Submit... \(name)") + print(view.value(forKey: "_autolayoutTrace")!) + } + + private var validToSubmit: AnyPublisher { + return Publishers.CombineLatest3($acceptedTerms, $acceptedPrivacy, $name) + .map { terms, privacy, name in + terms && privacy && !name.isBlank + }.eraseToAnyPublisher() + } + + // A longer approach just for fun: + // + // private var validName: AnyPublisher { + // return $name.map { name in + // guard !name.isBlank && name.count > 2 else { return nil } + // return name + // }.eraseToAnyPublisher() + // } + // + // private var acceptedAll: AnyPublisher { + // return Publishers.CombineLatest($acceptedTerms, $acceptedPrivacy) + // .map { terms, privacy in + // terms && privacy + // }.eraseToAnyPublisher() + // } + // + // private var validToSubmit: AnyPublisher { + // return Publishers.CombineLatest(acceptedAll, validName) + // .map { terms, name in + // terms && name != nil + // }.eraseToAnyPublisher() + // } +} + +#if DEBUG +import SwiftUI + +extension TermsViewController: UIViewControllerRepresentable { + func makeUIViewController(context: Context) -> TermsViewController { + let storyboard = UIStoryboard(name: "Main", bundle: nil) + guard let viewController = storyboard.instantiateViewController(identifier: "TermsViewController") as? TermsViewController else { + fatalError("Cannot load from storyboard") + } + return viewController + } + + func updateUIViewController(_ uiViewController: TermsViewController, context: Context) { + } +} +#endif diff --git a/Validate/Validate/TermsViewControllerPreviews.swift b/Validate/Validate/TermsViewControllerPreviews.swift new file mode 100644 index 0000000..d32af21 --- /dev/null +++ b/Validate/Validate/TermsViewControllerPreviews.swift @@ -0,0 +1,71 @@ +// Created by Keith Harrison https://useyourloaf.com +// Copyright © 2020 Keith Harrison. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#if DEBUG +import SwiftUI + +let devices = [ + "iPhone SE", + "iPhone 11", + "iPad Pro (11-inch) (2nd generation)" +] + +let sizeCategories: [ContentSizeCategory] = [ + .extraSmall, + .extraExtraExtraLarge, + .accessibilityExtraExtraExtraLarge +] + +struct TermsViewControllerPreviews: PreviewProvider { + static var previews: some View { + Group { + TermsViewController() + .previewDisplayName("Default") + + ForEach(devices, id: \.self) { name in + TermsViewController() + .previewDevice(PreviewDevice(rawValue: name)) + .previewDisplayName(name) + } + + ForEach(ColorScheme.allCases, id: \.self) { scheme in + TermsViewController() + .environment(\.colorScheme, scheme) + .previewDisplayName("\(scheme)") + } + + ForEach(sizeCategories, id: \.self) { size in + TermsViewController() + .environment(\.sizeCategory, size) + .previewDisplayName("\(size)") + } + } + } +} +#endif