From 4be30037ffa8672d7981ba71073984f2e89436d1 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Wed, 11 Oct 2017 17:26:40 +0100 Subject: [PATCH 01/85] Added git ignore file --- .gitignore | 190 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100755 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..a7d7348 --- /dev/null +++ b/.gitignore @@ -0,0 +1,190 @@ +##### +# OS X temporary files that should never be committed +# +# c.f. http://www.westwind.com/reference/os-x/invisibles.html + +.DS_Store + +# c.f. http://www.westwind.com/reference/os-x/invisibles.html + +.Trashes + +# c.f. http://www.westwind.com/reference/os-x/invisibles.html + +*.swp + +# *.lock - this is used and abused by many editors for many different things. +# For the main ones I use (e.g. Eclipse), it should be excluded +# from source-control, but YMMV + +*.lock + +# +# profile - REMOVED temporarily (on double-checking, this seems incorrect; I can't find it in OS X docs?) +#profile + + +#### +# Xcode temporary files that should never be committed +# +# NB: NIB/XIB files still exist even on Storyboard projects, so we want this... + +*~.nib + + +#### +# Xcode build files - +# +# NB: slash on the end, so we only remove the FOLDER, not any files that were badly named "DerivedData" + +DerivedData/ + +# NB: slash on the end, so we only remove the FOLDER, not any files that were badly named "build" + +build/ + + +##### +# Xcode private settings (window sizes, bookmarks, breakpoints, custom executables, smart groups) +# +# This is complicated: +# +# SOMETIMES you need to put this file in version control. +# Apple designed it poorly - if you use "custom executables", they are +# saved in this file. +# 99% of projects do NOT use those, so they do NOT want to version control this file. +# ..but if you're in the 1%, comment out the line "*.pbxuser" + +# .pbxuser: http://lists.apple.com/archives/xcode-users/2004/Jan/msg00193.html + +*.pbxuser + +# .mode1v3: http://lists.apple.com/archives/xcode-users/2007/Oct/msg00465.html + +*.mode1v3 + +# .mode2v3: http://lists.apple.com/archives/xcode-users/2007/Oct/msg00465.html + +*.mode2v3 + +# .perspectivev3: http://stackoverflow.com/questions/5223297/xcode-projects-what-is-a-perspectivev3-file + +*.perspectivev3 + +# NB: also, whitelist the default ones, some projects need to use these +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + + +#### +# Xcode 4 - semi-personal settings +# +# +# OPTION 1: --------------------------------- +# throw away ALL personal settings (including custom schemes! +# - unless they are "shared") +# +# NB: this is exclusive with OPTION 2 below +xcuserdata + +# OPTION 2: --------------------------------- +# get rid of ALL personal settings, but KEEP SOME OF THEM +# - NB: you must manually uncomment the bits you want to keep +# +# NB: this is exclusive with OPTION 1 above +# +#xcuserdata/**/* + +# (requires option 2 above): Personal Schemes +# +#!xcuserdata/**/xcschemes/* + +#### +# XCode 4 workspaces - more detailed +# +# Workspaces are important! They are a core feature of Xcode - don't exclude them :) +# +# Workspace layout is quite spammy. For reference: +# +# /(root)/ +# /(project-name).xcodeproj/ +# project.pbxproj +# /project.xcworkspace/ +# contents.xcworkspacedata +# /xcuserdata/ +# /(your name)/xcuserdatad/ +# UserInterfaceState.xcuserstate +# /xcsshareddata/ +# /xcschemes/ +# (shared scheme name).xcscheme +# /xcuserdata/ +# /(your name)/xcuserdatad/ +# (private scheme).xcscheme +# xcschememanagement.plist +# +# + +#### +# Xcode 4 - Deprecated classes +# +# Allegedly, if you manually "deprecate" your classes, they get moved here. +# +# We're using source-control, so this is a "feature" that we do not want! + +*.moved-aside + +#### +# UNKNOWN: recommended by others, but I can't discover what these files are +# +# ...none. Everything is now explained. + +#### +# +# Pods and Gems +# +/pods +.bundle/ + +# Builds +*.ipa + +# ssl certificates +*.crt +.idea/.name +.idea/babylon-partners.iml +.idea/encodings.xml +.idea/misc.xml +.idea/modules.xml +.idea/runConfigurations/Babylon.xml +.idea/runConfigurations/BabylonAnalysis.xml +.idea/scopes/scope_settings.xml +.idea/vcs.xml +.idea/workspace.xml +.idea/xcode.xml + +Iconr\n +Crashlytics.framework +Crashlytics.framework/ +Crashlytics.* +Crashlytics +Babylon/dist/ +dist.zip +Babylon.xcworkspace +Babylon.xcworkspace/ +Pods/ +Pods + +# Orig files skipped +*.orig + +Carthage/Checkouts/* +backboneLocalizationBuilder +.idea/babylon-ios.iml +.idea/runConfigurations/Babylon_STAGING1.xml + +# Fastlane +fastlane/README.md +fastlane/report.xml +vendor/ From b392ccad7aff4c32c123ab41a23ccc7c02e29434 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Wed, 11 Oct 2017 17:35:10 +0100 Subject: [PATCH 02/85] Added project workspace --- DrawerKit.xcworkspace/contents.xcworkspacedata | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 DrawerKit.xcworkspace/contents.xcworkspacedata diff --git a/DrawerKit.xcworkspace/contents.xcworkspacedata b/DrawerKit.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..94b2795 --- /dev/null +++ b/DrawerKit.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,4 @@ + + + From 4895d9585ed6265a21306dbbe74a09b2d9f64934 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Wed, 11 Oct 2017 17:41:27 +0100 Subject: [PATCH 03/85] Added tab bar template project --- .../contents.xcworkspacedata | 3 + .../DrawerKitDemo.xcodeproj/project.pbxproj | 331 ++++++++++++++++++ DrawerKitDemo/DrawerKitDemo/AppDelegate.swift | 46 +++ .../AppIcon.appiconset/Contents.json | 68 ++++ .../first.imageset/Contents.json | 12 + .../Assets.xcassets/first.imageset/first.pdf | Bin 0 -> 2465 bytes .../second.imageset/Contents.json | 12 + .../second.imageset/second.pdf | Bin 0 -> 2423 bytes .../Base.lproj/LaunchScreen.storyboard | 25 ++ .../DrawerKitDemo/Base.lproj/Main.storyboard | 102 ++++++ .../DrawerKitDemo/FirstViewController.swift | 25 ++ DrawerKitDemo/DrawerKitDemo/Info.plist | 57 +++ .../DrawerKitDemo/SecondViewController.swift | 25 ++ 13 files changed, 706 insertions(+) create mode 100644 DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj create mode 100644 DrawerKitDemo/DrawerKitDemo/AppDelegate.swift create mode 100644 DrawerKitDemo/DrawerKitDemo/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 DrawerKitDemo/DrawerKitDemo/Assets.xcassets/first.imageset/Contents.json create mode 100644 DrawerKitDemo/DrawerKitDemo/Assets.xcassets/first.imageset/first.pdf create mode 100644 DrawerKitDemo/DrawerKitDemo/Assets.xcassets/second.imageset/Contents.json create mode 100644 DrawerKitDemo/DrawerKitDemo/Assets.xcassets/second.imageset/second.pdf create mode 100644 DrawerKitDemo/DrawerKitDemo/Base.lproj/LaunchScreen.storyboard create mode 100644 DrawerKitDemo/DrawerKitDemo/Base.lproj/Main.storyboard create mode 100644 DrawerKitDemo/DrawerKitDemo/FirstViewController.swift create mode 100644 DrawerKitDemo/DrawerKitDemo/Info.plist create mode 100644 DrawerKitDemo/DrawerKitDemo/SecondViewController.swift diff --git a/DrawerKit.xcworkspace/contents.xcworkspacedata b/DrawerKit.xcworkspace/contents.xcworkspacedata index 94b2795..65541c8 100644 --- a/DrawerKit.xcworkspace/contents.xcworkspacedata +++ b/DrawerKit.xcworkspace/contents.xcworkspacedata @@ -1,4 +1,7 @@ + + diff --git a/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj b/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj new file mode 100644 index 0000000..01747a6 --- /dev/null +++ b/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj @@ -0,0 +1,331 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 48; + objects = { + +/* Begin PBXBuildFile section */ + CBBA2D3A1F8E807400E0137F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBA2D391F8E807400E0137F /* AppDelegate.swift */; }; + CBBA2D3C1F8E807400E0137F /* FirstViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBA2D3B1F8E807400E0137F /* FirstViewController.swift */; }; + CBBA2D3E1F8E807400E0137F /* SecondViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBA2D3D1F8E807400E0137F /* SecondViewController.swift */; }; + CBBA2D411F8E807400E0137F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CBBA2D3F1F8E807400E0137F /* Main.storyboard */; }; + CBBA2D431F8E807400E0137F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CBBA2D421F8E807400E0137F /* Assets.xcassets */; }; + CBBA2D461F8E807400E0137F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CBBA2D441F8E807400E0137F /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + CBBA2D361F8E807400E0137F /* DrawerKitDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DrawerKitDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; + CBBA2D391F8E807400E0137F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + CBBA2D3B1F8E807400E0137F /* FirstViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstViewController.swift; sourceTree = ""; }; + CBBA2D3D1F8E807400E0137F /* SecondViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondViewController.swift; sourceTree = ""; }; + CBBA2D401F8E807400E0137F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + CBBA2D421F8E807400E0137F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + CBBA2D451F8E807400E0137F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + CBBA2D471F8E807400E0137F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + CBBA2D331F8E807300E0137F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + CBBA2D2D1F8E807300E0137F = { + isa = PBXGroup; + children = ( + CBBA2D381F8E807400E0137F /* DrawerKitDemo */, + CBBA2D371F8E807400E0137F /* Products */, + ); + sourceTree = ""; + }; + CBBA2D371F8E807400E0137F /* Products */ = { + isa = PBXGroup; + children = ( + CBBA2D361F8E807400E0137F /* DrawerKitDemo.app */, + ); + name = Products; + sourceTree = ""; + }; + CBBA2D381F8E807400E0137F /* DrawerKitDemo */ = { + isa = PBXGroup; + children = ( + CBBA2D391F8E807400E0137F /* AppDelegate.swift */, + CBBA2D3B1F8E807400E0137F /* FirstViewController.swift */, + CBBA2D3D1F8E807400E0137F /* SecondViewController.swift */, + CBBA2D421F8E807400E0137F /* Assets.xcassets */, + CBBA2D3F1F8E807400E0137F /* Main.storyboard */, + CBBA2D441F8E807400E0137F /* LaunchScreen.storyboard */, + CBBA2D471F8E807400E0137F /* Info.plist */, + ); + path = DrawerKitDemo; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + CBBA2D351F8E807300E0137F /* DrawerKitDemo */ = { + isa = PBXNativeTarget; + buildConfigurationList = CBBA2D4A1F8E807400E0137F /* Build configuration list for PBXNativeTarget "DrawerKitDemo" */; + buildPhases = ( + CBBA2D321F8E807300E0137F /* Sources */, + CBBA2D331F8E807300E0137F /* Frameworks */, + CBBA2D341F8E807300E0137F /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = DrawerKitDemo; + productName = DrawerKitDemo; + productReference = CBBA2D361F8E807400E0137F /* DrawerKitDemo.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + CBBA2D2E1F8E807300E0137F /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0900; + LastUpgradeCheck = 0900; + ORGANIZATIONNAME = "Babylon Health"; + TargetAttributes = { + CBBA2D351F8E807300E0137F = { + CreatedOnToolsVersion = 9.0; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = CBBA2D311F8E807300E0137F /* Build configuration list for PBXProject "DrawerKitDemo" */; + compatibilityVersion = "Xcode 8.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = CBBA2D2D1F8E807300E0137F; + productRefGroup = CBBA2D371F8E807400E0137F /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + CBBA2D351F8E807300E0137F /* DrawerKitDemo */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + CBBA2D341F8E807300E0137F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CBBA2D461F8E807400E0137F /* LaunchScreen.storyboard in Resources */, + CBBA2D431F8E807400E0137F /* Assets.xcassets in Resources */, + CBBA2D411F8E807400E0137F /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + CBBA2D321F8E807300E0137F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CBBA2D3E1F8E807400E0137F /* SecondViewController.swift in Sources */, + CBBA2D3A1F8E807400E0137F /* AppDelegate.swift in Sources */, + CBBA2D3C1F8E807400E0137F /* FirstViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + CBBA2D3F1F8E807400E0137F /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + CBBA2D401F8E807400E0137F /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + CBBA2D441F8E807400E0137F /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + CBBA2D451F8E807400E0137F /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + CBBA2D481F8E807400E0137F /* 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_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.3; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + CBBA2D491F8E807400E0137F /* 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_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.3; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + CBBA2D4B1F8E807400E0137F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = TK5G2RYR3R; + INFOPLIST_FILE = DrawerKitDemo/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.babylonhealth.DrawerKitDemo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + CBBA2D4C1F8E807400E0137F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = TK5G2RYR3R; + INFOPLIST_FILE = DrawerKitDemo/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.babylonhealth.DrawerKitDemo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + CBBA2D311F8E807300E0137F /* Build configuration list for PBXProject "DrawerKitDemo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CBBA2D481F8E807400E0137F /* Debug */, + CBBA2D491F8E807400E0137F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + CBBA2D4A1F8E807400E0137F /* Build configuration list for PBXNativeTarget "DrawerKitDemo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CBBA2D4B1F8E807400E0137F /* Debug */, + CBBA2D4C1F8E807400E0137F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = CBBA2D2E1F8E807300E0137F /* Project object */; +} diff --git a/DrawerKitDemo/DrawerKitDemo/AppDelegate.swift b/DrawerKitDemo/DrawerKitDemo/AppDelegate.swift new file mode 100644 index 0000000..4cf8854 --- /dev/null +++ b/DrawerKitDemo/DrawerKitDemo/AppDelegate.swift @@ -0,0 +1,46 @@ +// +// AppDelegate.swift +// DrawerKitDemo +// +// Created by Wagner Truppel on 11/10/2017. +// Copyright © 2017 Babylon Health. All rights reserved. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + func applicationWillResignActive(_ application: UIApplication) { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. + } + + func applicationDidEnterBackground(_ application: UIApplication) { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. + } + + func applicationWillEnterForeground(_ application: UIApplication) { + // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. + } + + func applicationDidBecomeActive(_ application: UIApplication) { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + } + + func applicationWillTerminate(_ application: UIApplication) { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. + } + + +} + diff --git a/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/AppIcon.appiconset/Contents.json b/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..36d2c80 --- /dev/null +++ b/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/first.imageset/Contents.json b/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/first.imageset/Contents.json new file mode 100644 index 0000000..33a7451 --- /dev/null +++ b/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/first.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "first.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/first.imageset/first.pdf b/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/first.imageset/first.pdf new file mode 100644 index 0000000000000000000000000000000000000000..47d911dea647d55983671ead4d08b6f6b3600715 GIT binary patch literal 2465 zcmai03se(l7FLvisY+L#f-EBsK`11Xkc1=%MIujmB|xQ!>5vQ%APFP`Mlf5nSPOKE z2-^C>7DZkM!J;Da5{kfrC?Kvp3J41-3shFXvhuJqKo*oer*r0H{{P-P_x0cTzLzjD!bE z0qT;#Q7l4Gy%fMoXJaKT`@{5#R(MOqJPwQifv8iK6A%Ot9L14h2`38T!2s4PM=1!< zmL06}VYAA|ay#jZRs>HpA%X+eQW4rufWU%d1w5GTy!X#LeZsF_+~ccZmn3Fi)v^Z; zIG;?uU*yLLEYs61tjD>gXOFvSWsh{48xJvPNqKrIJtMdCz2cA2aC7TF?b@K`V!Lw- zE;zpH&ApqhoRjAHt}gK}>(qAc8dvrkD31*`<5<%C(&4_LqhQ3-GOf8~n^leG?+-@@^pjPa$J2gW@O)!b9hdTJ zTauyIJ&~rqeEZC1p9dWgx7{_WRc2=drMO=wcT7B{Zd58z`d)r_36r4V<6PGkCjpGfbnVMtm@;b}F!T?)Qav$_y7H5T;Mf!T}M zWxP9TNqrV?e5;b|pWd3^u2KAatk&3aDB@0 zZBaQNdCZ2#fblzYZnRCCjQ-GQWb-s8bX&<)?SxnUGdDYVFVk`xIf7@g3~$DX`H71_;r5OSZz1p`$_8y_9 z{;MJJ+n=>7Ewg;GnGHoz)&ID0z@F2!e$F7cWQ?d6s(!VY)_Gw})xCyMvsD={5i&H* zAIr_ACo8;Se6<*!-mm9Am79Iz^RVlc?%S5sg|E*SyIV{dd9{Mpf#d3cih5WKt=%ps zBEo)bt8EjmeCFYJRYU|b7d`p+-V|X2wOCYtyLP6t=!WH-kgdf0A};ytPfZiCwVPx{ z`g;zpe{8a4RQxQUwVU02<4X3w|9h;}XjhGWquqn{vVqq8g{*}BQF9=ybe_TM*!M=rFs1pN)yzctIXAoicsCe6>fit>wgZ#vp^hZOY0`J`rx zwdSK?GwR_xm9;5XjH|vf{O+Yg-)z;s*xt>;-vU`D-_SK<=~4eEVQkV~57{vyH6ebu>DqwE~Mx_A31Lg~$ zy`i52ew?GEluE%AN>ju*5z~dx2QQLP-G+E>o|En2+s&<^4*d zWD+<8PXs_Jp7_5Dpi(Gg3J?M;%Rn0{dcxQRh!ip+iHJ_e`!bM1MGxwI8Iedr+x>wI zq@&04fsADHfBccq^gpm8Q_u-~Uj|Z$YwSR(^_mzUb!}Xb2Cj`uqphg}v_Y%?Fs=;= zWq3s{hy)Tu!ji@RJsd$C+G{F6V2j1*Xk+7xo|LO7M2yb)(wc=%x-AclP9pLsbTF7m tf?*P!2EjJu5MD57P32Q4pdJ3tO_shCDT2=K;$ebx8ymcd$v!V9{2$sQiO>K5 literal 0 HcmV?d00001 diff --git a/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/second.imageset/Contents.json b/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/second.imageset/Contents.json new file mode 100644 index 0000000..03bd9c9 --- /dev/null +++ b/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/second.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "second.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/second.imageset/second.pdf b/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/second.imageset/second.pdf new file mode 100644 index 0000000000000000000000000000000000000000..401614e288b4b160471c2776bed6f09762af3e1c GIT binary patch literal 2423 zcmai02~-nz8dqq7rWU#)x2z)&iMWtV?gK$2a)?|BP!TbkV}J-Gl1wmy+3G9S0=uOY zwCjai6uBOPMMdNiN`VDYKwLQr2#W^`R93*Ua=0^)EGYY4=grIf|KIohziYnv{elnR zdN_hk6db<(+3RPcxs!iut8c(j0TLh=9mY931H^rhEE0|aXo!dh5Iv+ZFr+}9F+v#P zLSnfD0$f~hN>~92V{z)WJA&2)5rW!|I?;o?oI*%zejJiuY>hFLQwKlAa9O+PnPh#% zJp~JAfs-De6u@;{cREMs}D-sGib_4>50m8_|Uj-zC0m@Qx zJV+^5Da4Qxpf3sBtKZs<_aI$Cb1T+h<->yL^rfiGNelYkqgC3#SqVP_iy*_RPcbjH)xYf zvYdC-)D_t8c{=5%;$VJQxs_#61J*kuXMC?|&PbPo-7uSxoNr~&J94|gKk+03JGXXM zx4w~=^VUzWqQveu_ilRgPdgNNdnLZCH`=;w91}cP5ihdm*B_jI+rk=Pz2Lk!)XUu| zkls3P5&YHJ!t0kF+MUVBHOAx)a zy|JxHNd37z*~b5#%*0w_F|#4?erNP;pG`Fevoq2%?9Y>%Cmzv1(ks;aPIGaiy1mhU zr{7lB^Mem&`QK{b{uiDC zLD8muY(FNk{YdV|zO#o_VB%);8|~Bh(Z4p?GM}g-HN2;q7~YNl-lD~HZ$taYw$Fzbn6c?c5eo=@`i>()el#qwB)hI-gp0 ztsw@dh-I5NJkk)RCoBC`L8ffKfY)=oG=#4LA9TO6eA}9~@a6dupB+=S{%s&NY}^0GY`hAqod)uxXHitrN%bWOJohV>t?!&Z#d4y^7J@=Oh`HV zee**4j>xRUd)|D0^I-B#<`m1-5PO{)!v(q?ecFYqr_8Q{+z7#n0uFbq%_?hZ-Ck(6NRn=vDS7Nb1Xxx_)9`!i- zGR>av*PJ+P_AGO*a_$FbtCCISg5PK44p&W#t$)+HVcxpR9LAL%_Pv$p`0tO*sYZD0 zCG3-ipr4WoVbjpw%Nf^r^rnWcDcjumgBPEjO;NM6QtZ8wU#0rjHx5zs%CahF4$W*U zxu!q8kYP+>v!BNPVIRJUGIWU@7qVvx2VZDw4gV8%z%bjpF#aF(W|PmliwlakuRq>= zhaCRc^R!7T)#Twb0{!Tns=CxGcGX|leErg`f3|v0eBXxHU&6Sj-_>zOL!!d+j5Xt* zPS;rWd^zEF{5d5%)nNK=)zFJ8Uc=aQKD%UK)_TNes1Gh{SD*Mb_@O` z7*zs9Uq~Vq?v|^8FcM-yLGMKzR3YFB0z@CFM2X-dio%Gu6&MJj=I?uS7sA3AdE^p8 zE49$PjJzPIJwfn;jso7X2#jynr3dO@p5QAK1O9wqKLC=PkQjj~p#n|-5O`ihbTzzu zL{}2QEe28pJYfVZRW56FL`LvO&|C^1Th{0}hQMW$Ng$QPpnzlwnMnf=kVy8(y==L% zLMa>r0gELdT + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DrawerKitDemo/DrawerKitDemo/Base.lproj/Main.storyboard b/DrawerKitDemo/DrawerKitDemo/Base.lproj/Main.storyboard new file mode 100644 index 0000000..53f6561 --- /dev/null +++ b/DrawerKitDemo/DrawerKitDemo/Base.lproj/Main.storyboard @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DrawerKitDemo/DrawerKitDemo/FirstViewController.swift b/DrawerKitDemo/DrawerKitDemo/FirstViewController.swift new file mode 100644 index 0000000..b34e9d3 --- /dev/null +++ b/DrawerKitDemo/DrawerKitDemo/FirstViewController.swift @@ -0,0 +1,25 @@ +// +// FirstViewController.swift +// DrawerKitDemo +// +// Created by Wagner Truppel on 11/10/2017. +// Copyright © 2017 Babylon Health. All rights reserved. +// + +import UIKit + +class FirstViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view, typically from a nib. + } + + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } + + +} + diff --git a/DrawerKitDemo/DrawerKitDemo/Info.plist b/DrawerKitDemo/DrawerKitDemo/Info.plist new file mode 100644 index 0000000..efd0255 --- /dev/null +++ b/DrawerKitDemo/DrawerKitDemo/Info.plist @@ -0,0 +1,57 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + DK Demo + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UIStatusBarTintParameters + + UINavigationBar + + Style + UIBarStyleDefault + Translucent + + + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/DrawerKitDemo/DrawerKitDemo/SecondViewController.swift b/DrawerKitDemo/DrawerKitDemo/SecondViewController.swift new file mode 100644 index 0000000..1f6c115 --- /dev/null +++ b/DrawerKitDemo/DrawerKitDemo/SecondViewController.swift @@ -0,0 +1,25 @@ +// +// SecondViewController.swift +// DrawerKitDemo +// +// Created by Wagner Truppel on 11/10/2017. +// Copyright © 2017 Babylon Health. All rights reserved. +// + +import UIKit + +class SecondViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view, typically from a nib. + } + + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } + + +} + From 632633d852898b97e3de01e966cb91f06814dc00 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Wed, 11 Oct 2017 17:44:00 +0100 Subject: [PATCH 04/85] Added DrawerKit empty framework --- .../contents.xcworkspacedata | 3 + DrawerKit/DrawerKit.xcodeproj/project.pbxproj | 319 ++++++++++++++++++ DrawerKit/DrawerKit/DrawerKit.h | 19 ++ DrawerKit/DrawerKit/Info.plist | 24 ++ .../DrawerKitDemo.xcodeproj/project.pbxproj | 20 ++ 5 files changed, 385 insertions(+) create mode 100644 DrawerKit/DrawerKit.xcodeproj/project.pbxproj create mode 100644 DrawerKit/DrawerKit/DrawerKit.h create mode 100644 DrawerKit/DrawerKit/Info.plist diff --git a/DrawerKit.xcworkspace/contents.xcworkspacedata b/DrawerKit.xcworkspace/contents.xcworkspacedata index 65541c8..c4147ee 100644 --- a/DrawerKit.xcworkspace/contents.xcworkspacedata +++ b/DrawerKit.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/DrawerKit/DrawerKit.xcodeproj/project.pbxproj b/DrawerKit/DrawerKit.xcodeproj/project.pbxproj new file mode 100644 index 0000000..b02e90e --- /dev/null +++ b/DrawerKit/DrawerKit.xcodeproj/project.pbxproj @@ -0,0 +1,319 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 48; + objects = { + +/* Begin PBXBuildFile section */ + CBBA2D5C1F8E815B00E0137F /* DrawerKit.h in Headers */ = {isa = PBXBuildFile; fileRef = CBBA2D5A1F8E815B00E0137F /* DrawerKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + CBBA2D571F8E815B00E0137F /* DrawerKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DrawerKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + CBBA2D5A1F8E815B00E0137F /* DrawerKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DrawerKit.h; sourceTree = ""; }; + CBBA2D5B1F8E815B00E0137F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + CBBA2D531F8E815B00E0137F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + CBBA2D4D1F8E815B00E0137F = { + isa = PBXGroup; + children = ( + CBBA2D591F8E815B00E0137F /* DrawerKit */, + CBBA2D581F8E815B00E0137F /* Products */, + ); + sourceTree = ""; + }; + CBBA2D581F8E815B00E0137F /* Products */ = { + isa = PBXGroup; + children = ( + CBBA2D571F8E815B00E0137F /* DrawerKit.framework */, + ); + name = Products; + sourceTree = ""; + }; + CBBA2D591F8E815B00E0137F /* DrawerKit */ = { + isa = PBXGroup; + children = ( + CBBA2D5A1F8E815B00E0137F /* DrawerKit.h */, + CBBA2D5B1F8E815B00E0137F /* Info.plist */, + ); + path = DrawerKit; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + CBBA2D541F8E815B00E0137F /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + CBBA2D5C1F8E815B00E0137F /* DrawerKit.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + CBBA2D561F8E815B00E0137F /* DrawerKit */ = { + isa = PBXNativeTarget; + buildConfigurationList = CBBA2D5F1F8E815B00E0137F /* Build configuration list for PBXNativeTarget "DrawerKit" */; + buildPhases = ( + CBBA2D521F8E815B00E0137F /* Sources */, + CBBA2D531F8E815B00E0137F /* Frameworks */, + CBBA2D541F8E815B00E0137F /* Headers */, + CBBA2D551F8E815B00E0137F /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = DrawerKit; + productName = DrawerKit; + productReference = CBBA2D571F8E815B00E0137F /* DrawerKit.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + CBBA2D4E1F8E815B00E0137F /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0900; + ORGANIZATIONNAME = "Babylon Health"; + TargetAttributes = { + CBBA2D561F8E815B00E0137F = { + CreatedOnToolsVersion = 9.0; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = CBBA2D511F8E815B00E0137F /* Build configuration list for PBXProject "DrawerKit" */; + compatibilityVersion = "Xcode 8.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = CBBA2D4D1F8E815B00E0137F; + productRefGroup = CBBA2D581F8E815B00E0137F /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + CBBA2D561F8E815B00E0137F /* DrawerKit */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + CBBA2D551F8E815B00E0137F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + CBBA2D521F8E815B00E0137F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + CBBA2D5D1F8E815B00E0137F /* 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_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + 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 = 10.3; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + CBBA2D5E1F8E815B00E0137F /* 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_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + 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 = 10.3; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + CBBA2D601F8E815B00E0137F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = TK5G2RYR3R; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = DrawerKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.babylonhealth.DrawerKit; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + CBBA2D611F8E815B00E0137F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = TK5G2RYR3R; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = DrawerKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.babylonhealth.DrawerKit; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + CBBA2D511F8E815B00E0137F /* Build configuration list for PBXProject "DrawerKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CBBA2D5D1F8E815B00E0137F /* Debug */, + CBBA2D5E1F8E815B00E0137F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + CBBA2D5F1F8E815B00E0137F /* Build configuration list for PBXNativeTarget "DrawerKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CBBA2D601F8E815B00E0137F /* Debug */, + CBBA2D611F8E815B00E0137F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = CBBA2D4E1F8E815B00E0137F /* Project object */; +} diff --git a/DrawerKit/DrawerKit/DrawerKit.h b/DrawerKit/DrawerKit/DrawerKit.h new file mode 100644 index 0000000..1d93658 --- /dev/null +++ b/DrawerKit/DrawerKit/DrawerKit.h @@ -0,0 +1,19 @@ +// +// DrawerKit.h +// DrawerKit +// +// Created by Wagner Truppel on 11/10/2017. +// Copyright © 2017 Babylon Health. All rights reserved. +// + +#import + +//! Project version number for DrawerKit. +FOUNDATION_EXPORT double DrawerKitVersionNumber; + +//! Project version string for DrawerKit. +FOUNDATION_EXPORT const unsigned char DrawerKitVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/DrawerKit/DrawerKit/Info.plist b/DrawerKit/DrawerKit/Info.plist new file mode 100644 index 0000000..1007fd9 --- /dev/null +++ b/DrawerKit/DrawerKit/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj b/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj index 01747a6..5121f50 100644 --- a/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj +++ b/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj @@ -13,8 +13,24 @@ CBBA2D411F8E807400E0137F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CBBA2D3F1F8E807400E0137F /* Main.storyboard */; }; CBBA2D431F8E807400E0137F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CBBA2D421F8E807400E0137F /* Assets.xcassets */; }; CBBA2D461F8E807400E0137F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CBBA2D441F8E807400E0137F /* LaunchScreen.storyboard */; }; + CBBA2D631F8E817400E0137F /* DrawerKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CBBA2D621F8E817400E0137F /* DrawerKit.framework */; }; + CBBA2D641F8E817400E0137F /* DrawerKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CBBA2D621F8E817400E0137F /* DrawerKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ +/* Begin PBXCopyFilesBuildPhase section */ + CBBA2D651F8E817400E0137F /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + CBBA2D641F8E817400E0137F /* DrawerKit.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ CBBA2D361F8E807400E0137F /* DrawerKitDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DrawerKitDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; CBBA2D391F8E807400E0137F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -24,6 +40,7 @@ CBBA2D421F8E807400E0137F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; CBBA2D451F8E807400E0137F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; CBBA2D471F8E807400E0137F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + CBBA2D621F8E817400E0137F /* DrawerKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DrawerKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -31,6 +48,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + CBBA2D631F8E817400E0137F /* DrawerKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -42,6 +60,7 @@ children = ( CBBA2D381F8E807400E0137F /* DrawerKitDemo */, CBBA2D371F8E807400E0137F /* Products */, + CBBA2D621F8E817400E0137F /* DrawerKit.framework */, ); sourceTree = ""; }; @@ -77,6 +96,7 @@ CBBA2D321F8E807300E0137F /* Sources */, CBBA2D331F8E807300E0137F /* Frameworks */, CBBA2D341F8E807300E0137F /* Resources */, + CBBA2D651F8E817400E0137F /* Embed Frameworks */, ); buildRules = ( ); From 5c186e9744c471d8e0e378d7d8334c6c5b36f1b1 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Wed, 11 Oct 2017 18:06:17 +0100 Subject: [PATCH 05/85] Added basic view controllers and storyboards --- .../DrawerKitDemo.xcodeproj/project.pbxproj | 60 +++- DrawerKitDemo/DrawerKitDemo/AppDelegate.swift | 38 +-- .../DrawerKitDemo/Base.lproj/Main.storyboard | 102 ------ .../DrawerKitDemo/FirstViewController.swift | 25 -- .../DrawerKitDemo/PresentedView.swift | 6 + .../PresentedViewController.swift | 16 + .../DrawerKitDemo/PresenterView.swift | 5 + .../PresenterViewController.swift | 168 ++++++++++ .../DrawerKitDemo/SecondViewController.swift | 25 -- .../Base.lproj/LaunchScreen.storyboard | 0 .../Storyboards/Base.lproj/Main.storyboard | 73 +++++ .../Base.lproj/PresentedVC.storyboard | 116 +++++++ .../Base.lproj/PresenterVC.storyboard | 293 ++++++++++++++++++ 13 files changed, 729 insertions(+), 198 deletions(-) delete mode 100644 DrawerKitDemo/DrawerKitDemo/Base.lproj/Main.storyboard delete mode 100644 DrawerKitDemo/DrawerKitDemo/FirstViewController.swift create mode 100644 DrawerKitDemo/DrawerKitDemo/PresentedView.swift create mode 100644 DrawerKitDemo/DrawerKitDemo/PresentedViewController.swift create mode 100644 DrawerKitDemo/DrawerKitDemo/PresenterView.swift create mode 100644 DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift delete mode 100644 DrawerKitDemo/DrawerKitDemo/SecondViewController.swift rename DrawerKitDemo/DrawerKitDemo/{ => Storyboards}/Base.lproj/LaunchScreen.storyboard (100%) create mode 100644 DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/Main.storyboard create mode 100644 DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresentedVC.storyboard create mode 100644 DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresenterVC.storyboard diff --git a/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj b/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj index 5121f50..f9c3882 100644 --- a/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj +++ b/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj @@ -8,13 +8,17 @@ /* Begin PBXBuildFile section */ CBBA2D3A1F8E807400E0137F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBA2D391F8E807400E0137F /* AppDelegate.swift */; }; - CBBA2D3C1F8E807400E0137F /* FirstViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBA2D3B1F8E807400E0137F /* FirstViewController.swift */; }; - CBBA2D3E1F8E807400E0137F /* SecondViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBA2D3D1F8E807400E0137F /* SecondViewController.swift */; }; CBBA2D411F8E807400E0137F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CBBA2D3F1F8E807400E0137F /* Main.storyboard */; }; CBBA2D431F8E807400E0137F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CBBA2D421F8E807400E0137F /* Assets.xcassets */; }; CBBA2D461F8E807400E0137F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CBBA2D441F8E807400E0137F /* LaunchScreen.storyboard */; }; CBBA2D631F8E817400E0137F /* DrawerKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CBBA2D621F8E817400E0137F /* DrawerKit.framework */; }; CBBA2D641F8E817400E0137F /* DrawerKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CBBA2D621F8E817400E0137F /* DrawerKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + CBBA2D6F1F8E83E300E0137F /* PresenterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBA2D6B1F8E83E300E0137F /* PresenterViewController.swift */; }; + CBBA2D701F8E83E300E0137F /* PresenterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBA2D6C1F8E83E300E0137F /* PresenterView.swift */; }; + CBBA2D711F8E83E300E0137F /* PresentedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBA2D6D1F8E83E300E0137F /* PresentedView.swift */; }; + CBBA2D721F8E83E300E0137F /* PresentedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBA2D6E1F8E83E300E0137F /* PresentedViewController.swift */; }; + CBBA2D731F8E855C00E0137F /* PresenterVC.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CBBA2D661F8E836100E0137F /* PresenterVC.storyboard */; }; + CBBA2D741F8E856100E0137F /* PresentedVC.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CBBA2D681F8E836100E0137F /* PresentedVC.storyboard */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -34,13 +38,17 @@ /* Begin PBXFileReference section */ CBBA2D361F8E807400E0137F /* DrawerKitDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DrawerKitDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; CBBA2D391F8E807400E0137F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - CBBA2D3B1F8E807400E0137F /* FirstViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstViewController.swift; sourceTree = ""; }; - CBBA2D3D1F8E807400E0137F /* SecondViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondViewController.swift; sourceTree = ""; }; CBBA2D401F8E807400E0137F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; CBBA2D421F8E807400E0137F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; CBBA2D451F8E807400E0137F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; CBBA2D471F8E807400E0137F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; CBBA2D621F8E817400E0137F /* DrawerKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DrawerKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + CBBA2D671F8E836100E0137F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/PresenterVC.storyboard; sourceTree = ""; }; + CBBA2D691F8E836100E0137F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/PresentedVC.storyboard; sourceTree = ""; }; + CBBA2D6B1F8E83E300E0137F /* PresenterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresenterViewController.swift; sourceTree = ""; }; + CBBA2D6C1F8E83E300E0137F /* PresenterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresenterView.swift; sourceTree = ""; }; + CBBA2D6D1F8E83E300E0137F /* PresentedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentedView.swift; sourceTree = ""; }; + CBBA2D6E1F8E83E300E0137F /* PresentedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentedViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -76,16 +84,28 @@ isa = PBXGroup; children = ( CBBA2D391F8E807400E0137F /* AppDelegate.swift */, - CBBA2D3B1F8E807400E0137F /* FirstViewController.swift */, - CBBA2D3D1F8E807400E0137F /* SecondViewController.swift */, + CBBA2D6B1F8E83E300E0137F /* PresenterViewController.swift */, + CBBA2D6C1F8E83E300E0137F /* PresenterView.swift */, + CBBA2D6E1F8E83E300E0137F /* PresentedViewController.swift */, + CBBA2D6D1F8E83E300E0137F /* PresentedView.swift */, CBBA2D421F8E807400E0137F /* Assets.xcassets */, - CBBA2D3F1F8E807400E0137F /* Main.storyboard */, - CBBA2D441F8E807400E0137F /* LaunchScreen.storyboard */, + CBBA2D6A1F8E839200E0137F /* Storyboards */, CBBA2D471F8E807400E0137F /* Info.plist */, ); path = DrawerKitDemo; sourceTree = ""; }; + CBBA2D6A1F8E839200E0137F /* Storyboards */ = { + isa = PBXGroup; + children = ( + CBBA2D3F1F8E807400E0137F /* Main.storyboard */, + CBBA2D661F8E836100E0137F /* PresenterVC.storyboard */, + CBBA2D681F8E836100E0137F /* PresentedVC.storyboard */, + CBBA2D441F8E807400E0137F /* LaunchScreen.storyboard */, + ); + path = Storyboards; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -146,8 +166,10 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + CBBA2D741F8E856100E0137F /* PresentedVC.storyboard in Resources */, CBBA2D461F8E807400E0137F /* LaunchScreen.storyboard in Resources */, CBBA2D431F8E807400E0137F /* Assets.xcassets in Resources */, + CBBA2D731F8E855C00E0137F /* PresenterVC.storyboard in Resources */, CBBA2D411F8E807400E0137F /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -159,9 +181,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - CBBA2D3E1F8E807400E0137F /* SecondViewController.swift in Sources */, CBBA2D3A1F8E807400E0137F /* AppDelegate.swift in Sources */, - CBBA2D3C1F8E807400E0137F /* FirstViewController.swift in Sources */, + CBBA2D721F8E83E300E0137F /* PresentedViewController.swift in Sources */, + CBBA2D711F8E83E300E0137F /* PresentedView.swift in Sources */, + CBBA2D6F1F8E83E300E0137F /* PresenterViewController.swift in Sources */, + CBBA2D701F8E83E300E0137F /* PresenterView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -184,6 +208,22 @@ name = LaunchScreen.storyboard; sourceTree = ""; }; + CBBA2D661F8E836100E0137F /* PresenterVC.storyboard */ = { + isa = PBXVariantGroup; + children = ( + CBBA2D671F8E836100E0137F /* Base */, + ); + name = PresenterVC.storyboard; + sourceTree = ""; + }; + CBBA2D681F8E836100E0137F /* PresentedVC.storyboard */ = { + isa = PBXVariantGroup; + children = ( + CBBA2D691F8E836100E0137F /* Base */, + ); + name = PresentedVC.storyboard; + sourceTree = ""; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ diff --git a/DrawerKitDemo/DrawerKitDemo/AppDelegate.swift b/DrawerKitDemo/DrawerKitDemo/AppDelegate.swift index 4cf8854..7a03119 100644 --- a/DrawerKitDemo/DrawerKitDemo/AppDelegate.swift +++ b/DrawerKitDemo/DrawerKitDemo/AppDelegate.swift @@ -1,11 +1,3 @@ -// -// AppDelegate.swift -// DrawerKitDemo -// -// Created by Wagner Truppel on 11/10/2017. -// Copyright © 2017 Babylon Health. All rights reserved. -// - import UIKit @UIApplicationMain @@ -13,34 +5,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. + func application(_ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { return true } - - func applicationWillResignActive(_ application: UIApplication) { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. - } - - func applicationDidEnterBackground(_ application: UIApplication) { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. - } - - func applicationWillEnterForeground(_ application: UIApplication) { - // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. - } - - func applicationDidBecomeActive(_ application: UIApplication) { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. - } - - func applicationWillTerminate(_ application: UIApplication) { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. - } - - } - diff --git a/DrawerKitDemo/DrawerKitDemo/Base.lproj/Main.storyboard b/DrawerKitDemo/DrawerKitDemo/Base.lproj/Main.storyboard deleted file mode 100644 index 53f6561..0000000 --- a/DrawerKitDemo/DrawerKitDemo/Base.lproj/Main.storyboard +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DrawerKitDemo/DrawerKitDemo/FirstViewController.swift b/DrawerKitDemo/DrawerKitDemo/FirstViewController.swift deleted file mode 100644 index b34e9d3..0000000 --- a/DrawerKitDemo/DrawerKitDemo/FirstViewController.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// FirstViewController.swift -// DrawerKitDemo -// -// Created by Wagner Truppel on 11/10/2017. -// Copyright © 2017 Babylon Health. All rights reserved. -// - -import UIKit - -class FirstViewController: UIViewController { - - override func viewDidLoad() { - super.viewDidLoad() - // Do any additional setup after loading the view, typically from a nib. - } - - override func didReceiveMemoryWarning() { - super.didReceiveMemoryWarning() - // Dispose of any resources that can be recreated. - } - - -} - diff --git a/DrawerKitDemo/DrawerKitDemo/PresentedView.swift b/DrawerKitDemo/DrawerKitDemo/PresentedView.swift new file mode 100644 index 0000000..5bf83ae --- /dev/null +++ b/DrawerKitDemo/DrawerKitDemo/PresentedView.swift @@ -0,0 +1,6 @@ +import UIKit + +class PresentedView: UIView { + @IBOutlet weak var dividerView: UIView! + @IBOutlet weak var targetView: UIView! +} diff --git a/DrawerKitDemo/DrawerKitDemo/PresentedViewController.swift b/DrawerKitDemo/DrawerKitDemo/PresentedViewController.swift new file mode 100644 index 0000000..d3a1f9b --- /dev/null +++ b/DrawerKitDemo/DrawerKitDemo/PresentedViewController.swift @@ -0,0 +1,16 @@ +import UIKit +import DrawerKit + +class PresentedViewController: UIViewController { + var fixedHeightConstraint: NSLayoutConstraint! + var hasFixedHeight = false + + override func loadView() { + super.loadView() + view.heightAnchor.constraint(equalToConstant: 290).isActive = hasFixedHeight + } + + @IBAction func dismissButtonTapped() { + dismiss(animated: true) + } +} diff --git a/DrawerKitDemo/DrawerKitDemo/PresenterView.swift b/DrawerKitDemo/DrawerKitDemo/PresenterView.swift new file mode 100644 index 0000000..39cdec6 --- /dev/null +++ b/DrawerKitDemo/DrawerKitDemo/PresenterView.swift @@ -0,0 +1,5 @@ +import UIKit + +class PresenterView: UIView { + @IBOutlet weak var targetView: UIView! +} diff --git a/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift b/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift new file mode 100644 index 0000000..ce6e98f --- /dev/null +++ b/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift @@ -0,0 +1,168 @@ +import UIKit +import DrawerKit + +class PresenterViewController: UIViewController { + + private static let defaultDuration: Float = 0.8 + private var _duration: TimeInterval = 0 + private var duration: Float { + get { return Float(_duration) } + set { + let currentDuration = Float(_duration) + guard newValue != currentDuration else { return } + set(duration: newValue) + } + } + + private var hasFixedHeight = false + private var coversStatusBar = true + private var supportsPartialExpansion = true + private var dismissesInStages = true + private var isDrawerDraggable = true + private var isDismissableByOutsideDrawerTaps = true + private var numberOfTapsForOutsideDrawerDismissal: Int = 1 + + @IBOutlet weak var hasFixedHeightSwitch: UISwitch! + @IBOutlet weak var coversStatusBarSwitch: UISwitch! + @IBOutlet weak var supportsPartialExpansionSwitch: UISwitch! + @IBOutlet weak var dismissesInStagesSwitch: UISwitch! + @IBOutlet weak var drawerDraggableSwitch: UISwitch! + @IBOutlet weak var dismissableByOutsideTapButton: UIButton! + @IBOutlet weak var durationSlider: UISlider! + @IBOutlet weak var durationField: UITextField! + + override func viewDidLoad() { + super.viewDidLoad() + setup() + } + + private static let formatter: NumberFormatter = { + let f = NumberFormatter() + f.allowsFloats = true + f.minimumIntegerDigits = 1 + f.minimumFractionDigits = 1 + f.maximumFractionDigits = 1 + return f + }() +} + +extension PresenterViewController { + private func doModalPresentation() { + let sb = UIStoryboard(name: "PresentedVC", bundle: nil) + guard let vc = sb.instantiateViewController(withIdentifier: "presented") + as? PresentedViewController else { return } + vc.hasFixedHeight = hasFixedHeight + present(vc, animated: true) + } +} + +extension PresenterViewController: UITextFieldDelegate { + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() + return true + } + + func textFieldDidEndEditing(_ textField: UITextField) { + guard let duration = Float(textField.text ?? "0") else { + self.duration = PresenterViewController.defaultDuration + return + } + self.duration = duration + } +} + +extension PresenterViewController { + @IBAction func targetViewTapped() { + doModalPresentation() + } + + @IBAction func sliderSideButtonTapped(sender: UIButton) { + handleSliderSideButtonTapped(sender) + } + + @IBAction func durationSliderChanged() { + guard duration != durationSlider.value else { return } + duration = durationSlider.value + } + + @IBAction func switchToggled(sender: UISwitch) { + handleSwitchToggled(sender) + } + + @IBAction func numberOfTapsButtonTapped(_ sender: UIButton) { + handleNumberOfTapsButtonTapped(sender) + } +} + +private extension PresenterViewController { + func setup() { + duration = PresenterViewController.defaultDuration + hasFixedHeightSwitch.isOn = hasFixedHeight + coversStatusBarSwitch.isOn = coversStatusBar + supportsPartialExpansionSwitch.isOn = supportsPartialExpansion + dismissesInStagesSwitch.isEnabled = supportsPartialExpansion + dismissesInStagesSwitch.isOn = dismissesInStages + drawerDraggableSwitch.isOn = isDrawerDraggable + dismissableByOutsideTapButton.setTitle("\(numberOfTapsForOutsideDrawerDismissal)", for: .normal) + } + + func handleSliderSideButtonTapped(_ button: UIButton) { + enum ButtonTag: Int { + case min = 0 + case max + } + guard let btnTag = ButtonTag(rawValue: button.tag) else { return } + switch btnTag { + case .min: + duration = durationSlider.minimumValue + case .max: + duration = durationSlider.maximumValue + } + } + + func handleSwitchToggled(_ toggler: UISwitch) { + switch toggler { + case hasFixedHeightSwitch: + hasFixedHeight = toggler.isOn + case coversStatusBarSwitch: + coversStatusBar = toggler.isOn + case supportsPartialExpansionSwitch: + supportsPartialExpansion = toggler.isOn + dismissesInStagesSwitch.isEnabled = toggler.isOn + case dismissesInStagesSwitch: + dismissesInStages = toggler.isOn + case drawerDraggableSwitch: + isDrawerDraggable = toggler.isOn + default: + return + } + } + + func handleNumberOfTapsButtonTapped(_ button: UIButton) { + let curValue = Int(button.titleLabel?.text ?? "0") ?? 0 + let newValue = (curValue + 1) % 4 + button.setTitle("\(newValue)", for: .normal) + switch button { + case dismissableByOutsideTapButton: + isDismissableByOutsideDrawerTaps = (newValue > 0) + numberOfTapsForOutsideDrawerDismissal = newValue + default: + return + } + } + + func set(duration: Float) { + let sanitisedDuration = sanitize(duration: duration) + let nsDuration = NSNumber(value: sanitisedDuration) + durationField.text = PresenterViewController.formatter.string(from: nsDuration) + durationSlider.value = sanitisedDuration + _duration = TimeInterval(sanitisedDuration) + } + + func sanitize(duration: Float) -> Float { + let minDuration = durationSlider.minimumValue + let maxDuration = durationSlider.maximumValue + let value = min(maxDuration, max(duration, minDuration)) + return Float(truncf(10 * value)) / 10 + } +} diff --git a/DrawerKitDemo/DrawerKitDemo/SecondViewController.swift b/DrawerKitDemo/DrawerKitDemo/SecondViewController.swift deleted file mode 100644 index 1f6c115..0000000 --- a/DrawerKitDemo/DrawerKitDemo/SecondViewController.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// SecondViewController.swift -// DrawerKitDemo -// -// Created by Wagner Truppel on 11/10/2017. -// Copyright © 2017 Babylon Health. All rights reserved. -// - -import UIKit - -class SecondViewController: UIViewController { - - override func viewDidLoad() { - super.viewDidLoad() - // Do any additional setup after loading the view, typically from a nib. - } - - override func didReceiveMemoryWarning() { - super.didReceiveMemoryWarning() - // Dispose of any resources that can be recreated. - } - - -} - diff --git a/DrawerKitDemo/DrawerKitDemo/Base.lproj/LaunchScreen.storyboard b/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from DrawerKitDemo/DrawerKitDemo/Base.lproj/LaunchScreen.storyboard rename to DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/LaunchScreen.storyboard diff --git a/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/Main.storyboard b/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/Main.storyboard new file mode 100644 index 0000000..479f9f9 --- /dev/null +++ b/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/Main.storyboard @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresentedVC.storyboard b/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresentedVC.storyboard new file mode 100644 index 0000000..8dd5965 --- /dev/null +++ b/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresentedVC.storyboard @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresenterVC.storyboard b/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresenterVC.storyboard new file mode 100644 index 0000000..e1020cb --- /dev/null +++ b/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresenterVC.storyboard @@ -0,0 +1,293 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From aa0f36fc39bd3996b4a775f5d194d7cacdb027d7 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Wed, 11 Oct 2017 18:27:36 +0100 Subject: [PATCH 06/85] Fixed broken outlets --- .../DrawerKitDemo/PresentedView.swift | 1 - .../PresentedViewController.swift | 1 - .../DrawerKitDemo/PresenterView.swift | 4 +- .../PresenterViewController.swift | 2 +- .../Base.lproj/PresentedVC.storyboard | 14 +++-- .../Base.lproj/PresenterVC.storyboard | 62 +++++++++++++++++-- 6 files changed, 66 insertions(+), 18 deletions(-) diff --git a/DrawerKitDemo/DrawerKitDemo/PresentedView.swift b/DrawerKitDemo/DrawerKitDemo/PresentedView.swift index 5bf83ae..69da8ca 100644 --- a/DrawerKitDemo/DrawerKitDemo/PresentedView.swift +++ b/DrawerKitDemo/DrawerKitDemo/PresentedView.swift @@ -2,5 +2,4 @@ import UIKit class PresentedView: UIView { @IBOutlet weak var dividerView: UIView! - @IBOutlet weak var targetView: UIView! } diff --git a/DrawerKitDemo/DrawerKitDemo/PresentedViewController.swift b/DrawerKitDemo/DrawerKitDemo/PresentedViewController.swift index d3a1f9b..94965c9 100644 --- a/DrawerKitDemo/DrawerKitDemo/PresentedViewController.swift +++ b/DrawerKitDemo/DrawerKitDemo/PresentedViewController.swift @@ -2,7 +2,6 @@ import UIKit import DrawerKit class PresentedViewController: UIViewController { - var fixedHeightConstraint: NSLayoutConstraint! var hasFixedHeight = false override func loadView() { diff --git a/DrawerKitDemo/DrawerKitDemo/PresenterView.swift b/DrawerKitDemo/DrawerKitDemo/PresenterView.swift index 39cdec6..c524419 100644 --- a/DrawerKitDemo/DrawerKitDemo/PresenterView.swift +++ b/DrawerKitDemo/DrawerKitDemo/PresenterView.swift @@ -1,5 +1,3 @@ import UIKit -class PresenterView: UIView { - @IBOutlet weak var targetView: UIView! -} +class PresenterView: UIView {} diff --git a/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift b/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift index ce6e98f..32b0cf2 100644 --- a/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift +++ b/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift @@ -72,7 +72,7 @@ extension PresenterViewController: UITextFieldDelegate { } extension PresenterViewController { - @IBAction func targetViewTapped() { + @IBAction func presentButtonTapped() { doModalPresentation() } diff --git a/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresentedVC.storyboard b/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresentedVC.storyboard index 8dd5965..837cf0c 100644 --- a/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresentedVC.storyboard +++ b/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresentedVC.storyboard @@ -12,12 +12,12 @@ - + - + @@ -66,11 +66,11 @@ @@ -103,8 +106,7 @@ - - + diff --git a/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresenterVC.storyboard b/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresenterVC.storyboard index e1020cb..87b38b9 100644 --- a/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresenterVC.storyboard +++ b/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresenterVC.storyboard @@ -12,17 +12,17 @@ - + - + - + @@ -38,6 +38,9 @@ + + + @@ -63,6 +66,9 @@ + + + @@ -89,6 +95,9 @@ + + + @@ -123,6 +132,9 @@ + + + @@ -151,6 +163,9 @@ + + + @@ -181,6 +196,9 @@ + + + @@ -219,6 +237,9 @@ + + + @@ -237,6 +258,9 @@ + + + @@ -252,12 +276,28 @@ + + + + + + + @@ -268,21 +308,31 @@ + - + - - + + + + + + + + + + + From 1e79e2ff3e488d4e17f7115b4cab3de35f90e53a Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Wed, 11 Oct 2017 23:09:22 +0100 Subject: [PATCH 07/85] Added implementation of DrawerKit. A few bits to complete still. --- DrawerKit/DrawerKit.xcodeproj/project.pbxproj | 92 ++++++ ...DisplayController+InternalExtensions.swift | 26 ++ .../TimingConfiguration+Extensions.swift | 8 + .../TransitionConfiguration+Extensions.swift | 18 ++ .../Internal API/PresentationController.swift | 261 ++++++++++++++++++ .../Internal API/TransitionAnimator.swift | 55 ++++ .../Internal API/TransitionGeometry.swift | 27 ++ .../DrawerDisplayController+Extensions.swift | 53 ++++ .../Public API/DrawerDisplayController.swift | 17 ++ .../Protocols/DrawerPresentable.swift | 12 + .../Protocols/DrawerPresenting.swift | 6 + .../Structs/DrawerConfiguration.swift | 48 ++++ .../Structs/TimingConfiguration.swift | 12 + 13 files changed, 635 insertions(+) create mode 100755 DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+InternalExtensions.swift create mode 100644 DrawerKit/DrawerKit/Internal API/Extensions/TimingConfiguration+Extensions.swift create mode 100644 DrawerKit/DrawerKit/Internal API/Extensions/TransitionConfiguration+Extensions.swift create mode 100644 DrawerKit/DrawerKit/Internal API/PresentationController.swift create mode 100644 DrawerKit/DrawerKit/Internal API/TransitionAnimator.swift create mode 100644 DrawerKit/DrawerKit/Internal API/TransitionGeometry.swift create mode 100755 DrawerKit/DrawerKit/Public API/DrawerDisplayController+Extensions.swift create mode 100755 DrawerKit/DrawerKit/Public API/DrawerDisplayController.swift create mode 100644 DrawerKit/DrawerKit/Public API/Protocols/DrawerPresentable.swift create mode 100644 DrawerKit/DrawerKit/Public API/Protocols/DrawerPresenting.swift create mode 100644 DrawerKit/DrawerKit/Public API/Structs/DrawerConfiguration.swift create mode 100644 DrawerKit/DrawerKit/Public API/Structs/TimingConfiguration.swift diff --git a/DrawerKit/DrawerKit.xcodeproj/project.pbxproj b/DrawerKit/DrawerKit.xcodeproj/project.pbxproj index b02e90e..3d68a5d 100644 --- a/DrawerKit/DrawerKit.xcodeproj/project.pbxproj +++ b/DrawerKit/DrawerKit.xcodeproj/project.pbxproj @@ -7,10 +7,34 @@ objects = { /* Begin PBXBuildFile section */ + CB2CB7941F8E934500AA152D /* DrawerPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB78D1F8E934500AA152D /* DrawerPresentable.swift */; }; + CB2CB7951F8E934500AA152D /* DrawerPresenting.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB78E1F8E934500AA152D /* DrawerPresenting.swift */; }; + CB2CB7971F8E934500AA152D /* DrawerConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7911F8E934500AA152D /* DrawerConfiguration.swift */; }; + CB2CB79D1F8E951900AA152D /* DrawerDisplayController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB79A1F8E951900AA152D /* DrawerDisplayController.swift */; }; + CB2CB79E1F8E951900AA152D /* PresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB79B1F8E951900AA152D /* PresentationController.swift */; }; + CB2CB79F1F8E951900AA152D /* TransitionAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB79C1F8E951900AA152D /* TransitionAnimator.swift */; }; + CB2CB7A31F8E966E00AA152D /* TimingConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7A21F8E966D00AA152D /* TimingConfiguration.swift */; }; + CB2CB7A91F8E9AC000AA152D /* TimingConfiguration+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7A51F8E98F100AA152D /* TimingConfiguration+Extensions.swift */; }; + CB2CB7AA1F8E9AC600AA152D /* TransitionConfiguration+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7A61F8E98F100AA152D /* TransitionConfiguration+Extensions.swift */; }; + CB2CB7AB1F8E9BB400AA152D /* TransitionGeometry.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7921F8E934500AA152D /* TransitionGeometry.swift */; }; + CB2CB7AD1F8E9D9900AA152D /* DrawerDisplayController+InternalExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7AC1F8E9D9900AA152D /* DrawerDisplayController+InternalExtensions.swift */; }; + CB2CB7AF1F8E9F4C00AA152D /* DrawerDisplayController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7AE1F8E9F4C00AA152D /* DrawerDisplayController+Extensions.swift */; }; CBBA2D5C1F8E815B00E0137F /* DrawerKit.h in Headers */ = {isa = PBXBuildFile; fileRef = CBBA2D5A1F8E815B00E0137F /* DrawerKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + CB2CB78D1F8E934500AA152D /* DrawerPresentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DrawerPresentable.swift; sourceTree = ""; }; + CB2CB78E1F8E934500AA152D /* DrawerPresenting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DrawerPresenting.swift; sourceTree = ""; }; + CB2CB7911F8E934500AA152D /* DrawerConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DrawerConfiguration.swift; sourceTree = ""; }; + CB2CB7921F8E934500AA152D /* TransitionGeometry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransitionGeometry.swift; sourceTree = ""; }; + CB2CB79A1F8E951900AA152D /* DrawerDisplayController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DrawerDisplayController.swift; sourceTree = ""; }; + CB2CB79B1F8E951900AA152D /* PresentationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentationController.swift; sourceTree = ""; }; + CB2CB79C1F8E951900AA152D /* TransitionAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransitionAnimator.swift; sourceTree = ""; }; + CB2CB7A21F8E966D00AA152D /* TimingConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimingConfiguration.swift; sourceTree = ""; }; + CB2CB7A51F8E98F100AA152D /* TimingConfiguration+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimingConfiguration+Extensions.swift"; sourceTree = ""; }; + CB2CB7A61F8E98F100AA152D /* TransitionConfiguration+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TransitionConfiguration+Extensions.swift"; sourceTree = ""; }; + CB2CB7AC1F8E9D9900AA152D /* DrawerDisplayController+InternalExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DrawerDisplayController+InternalExtensions.swift"; sourceTree = ""; }; + CB2CB7AE1F8E9F4C00AA152D /* DrawerDisplayController+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DrawerDisplayController+Extensions.swift"; sourceTree = ""; }; CBBA2D571F8E815B00E0137F /* DrawerKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DrawerKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; CBBA2D5A1F8E815B00E0137F /* DrawerKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DrawerKit.h; sourceTree = ""; }; CBBA2D5B1F8E815B00E0137F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -27,6 +51,56 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + CB2CB7A01F8E962100AA152D /* Public API */ = { + isa = PBXGroup; + children = ( + CB2CB79A1F8E951900AA152D /* DrawerDisplayController.swift */, + CB2CB7AE1F8E9F4C00AA152D /* DrawerDisplayController+Extensions.swift */, + CB2CB7A71F8E9A8900AA152D /* Protocols */, + CB2CB7A81F8E9AA000AA152D /* Structs */, + ); + path = "Public API"; + sourceTree = ""; + }; + CB2CB7A11F8E963000AA152D /* Internal API */ = { + isa = PBXGroup; + children = ( + CB2CB79B1F8E951900AA152D /* PresentationController.swift */, + CB2CB79C1F8E951900AA152D /* TransitionAnimator.swift */, + CB2CB7921F8E934500AA152D /* TransitionGeometry.swift */, + CB2CB7A41F8E98F100AA152D /* Extensions */, + ); + path = "Internal API"; + sourceTree = ""; + }; + CB2CB7A41F8E98F100AA152D /* Extensions */ = { + isa = PBXGroup; + children = ( + CB2CB7AC1F8E9D9900AA152D /* DrawerDisplayController+InternalExtensions.swift */, + CB2CB7A61F8E98F100AA152D /* TransitionConfiguration+Extensions.swift */, + CB2CB7A51F8E98F100AA152D /* TimingConfiguration+Extensions.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + CB2CB7A71F8E9A8900AA152D /* Protocols */ = { + isa = PBXGroup; + children = ( + CB2CB78E1F8E934500AA152D /* DrawerPresenting.swift */, + CB2CB78D1F8E934500AA152D /* DrawerPresentable.swift */, + ); + path = Protocols; + sourceTree = ""; + }; + CB2CB7A81F8E9AA000AA152D /* Structs */ = { + isa = PBXGroup; + children = ( + CB2CB7911F8E934500AA152D /* DrawerConfiguration.swift */, + CB2CB7A21F8E966D00AA152D /* TimingConfiguration.swift */, + ); + path = Structs; + sourceTree = ""; + }; CBBA2D4D1F8E815B00E0137F = { isa = PBXGroup; children = ( @@ -46,6 +120,8 @@ CBBA2D591F8E815B00E0137F /* DrawerKit */ = { isa = PBXGroup; children = ( + CB2CB7A01F8E962100AA152D /* Public API */, + CB2CB7A11F8E963000AA152D /* Internal API */, CBBA2D5A1F8E815B00E0137F /* DrawerKit.h */, CBBA2D5B1F8E815B00E0137F /* Info.plist */, ); @@ -95,6 +171,7 @@ TargetAttributes = { CBBA2D561F8E815B00E0137F = { CreatedOnToolsVersion = 9.0; + LastSwiftMigration = 0900; ProvisioningStyle = Automatic; }; }; @@ -131,6 +208,18 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + CB2CB7941F8E934500AA152D /* DrawerPresentable.swift in Sources */, + CB2CB7AD1F8E9D9900AA152D /* DrawerDisplayController+InternalExtensions.swift in Sources */, + CB2CB7A91F8E9AC000AA152D /* TimingConfiguration+Extensions.swift in Sources */, + CB2CB79F1F8E951900AA152D /* TransitionAnimator.swift in Sources */, + CB2CB7951F8E934500AA152D /* DrawerPresenting.swift in Sources */, + CB2CB7AF1F8E9F4C00AA152D /* DrawerDisplayController+Extensions.swift in Sources */, + CB2CB7AB1F8E9BB400AA152D /* TransitionGeometry.swift in Sources */, + CB2CB7971F8E934500AA152D /* DrawerConfiguration.swift in Sources */, + CB2CB7AA1F8E9AC600AA152D /* TransitionConfiguration+Extensions.swift in Sources */, + CB2CB79D1F8E951900AA152D /* DrawerDisplayController.swift in Sources */, + CB2CB79E1F8E951900AA152D /* PresentationController.swift in Sources */, + CB2CB7A31F8E966E00AA152D /* TimingConfiguration.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -253,6 +342,7 @@ CBBA2D601F8E815B00E0137F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; DEFINES_MODULE = YES; @@ -266,6 +356,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.babylonhealth.DrawerKit; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -274,6 +365,7 @@ CBBA2D611F8E815B00E0137F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; DEFINES_MODULE = YES; diff --git a/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+InternalExtensions.swift b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+InternalExtensions.swift new file mode 100755 index 0000000..6253b61 --- /dev/null +++ b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+InternalExtensions.swift @@ -0,0 +1,26 @@ +import UIKit + +extension DrawerDisplayController: UIViewControllerTransitioningDelegate { + public func presentationController(forPresented presented: UIViewController, + presenting: UIViewController?, + source: UIViewController) -> UIPresentationController? { + guard let presentingVC = presenting else { return nil } + let presentationController = PresentationController(presentingVC: presentingVC, + presentedVC: presented, + configuration: configuration) + presentationController.delegate = self + return presentationController + } + + public func animationController(forPresented presented: UIViewController, + presenting: UIViewController, + source: UIViewController) -> UIViewControllerAnimatedTransitioning? { + return TransitionAnimator(isPresentation: true, configuration: configuration) + } + + public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { + return TransitionAnimator(isPresentation: false, configuration: configuration) + } +} + +extension DrawerDisplayController: UIAdaptivePresentationControllerDelegate {} // XXX diff --git a/DrawerKit/DrawerKit/Internal API/Extensions/TimingConfiguration+Extensions.swift b/DrawerKit/DrawerKit/Internal API/Extensions/TimingConfiguration+Extensions.swift new file mode 100644 index 0000000..4f877cb --- /dev/null +++ b/DrawerKit/DrawerKit/Internal API/Extensions/TimingConfiguration+Extensions.swift @@ -0,0 +1,8 @@ +import UIKit + +extension TimingConfiguration { + public static func ==(lhs: TimingConfiguration, rhs: TimingConfiguration) -> Bool { + return lhs.durationInSeconds == rhs.durationInSeconds + && lhs.timingCurveProvider === rhs.timingCurveProvider + } +} diff --git a/DrawerKit/DrawerKit/Internal API/Extensions/TransitionConfiguration+Extensions.swift b/DrawerKit/DrawerKit/Internal API/Extensions/TransitionConfiguration+Extensions.swift new file mode 100644 index 0000000..e7cc3a2 --- /dev/null +++ b/DrawerKit/DrawerKit/Internal API/Extensions/TransitionConfiguration+Extensions.swift @@ -0,0 +1,18 @@ +import UIKit + +extension DrawerConfiguration { + public static func ==(lhs: DrawerConfiguration, rhs: DrawerConfiguration) -> Bool { + return lhs.fullTransitionTimingConfiguration == rhs.fullTransitionTimingConfiguration + && lhs.partialTransitionTimingConfiguration == rhs.partialTransitionTimingConfiguration + && lhs.coversStatusBar == rhs.coversStatusBar + && lhs.supportsPartialExpansion == rhs.supportsPartialExpansion + && lhs.dismissesInStages == rhs.dismissesInStages + && lhs.isDrawerDraggable == rhs.isDrawerDraggable + && lhs.isDismissableByOutsideDrawerTaps == rhs.isDismissableByOutsideDrawerTaps + && lhs.numberOfTapsForOutsideDrawerDismissal == rhs.numberOfTapsForOutsideDrawerDismissal + && lhs.flickSpeedThreshold == rhs.flickSpeedThreshold + && lhs.upperMarkFraction == rhs.upperMarkFraction + && lhs.lowerMarkFraction == rhs.lowerMarkFraction + && lhs.maximumCornerRadius == rhs.maximumCornerRadius + } +} diff --git a/DrawerKit/DrawerKit/Internal API/PresentationController.swift b/DrawerKit/DrawerKit/Internal API/PresentationController.swift new file mode 100644 index 0000000..84cf09b --- /dev/null +++ b/DrawerKit/DrawerKit/Internal API/PresentationController.swift @@ -0,0 +1,261 @@ +import UIKit + +final class PresentationController: UIPresentationController { + private let configuration: DrawerConfiguration // intentionally immutable + private var lastDrawerY: CGFloat = 0 + private var containerViewDismissalTapGR: UITapGestureRecognizer? + private var presentedViewDragGR: UIPanGestureRecognizer? + + init(presentingVC: UIViewController, presentedVC: UIViewController, + configuration: DrawerConfiguration) { + self.configuration = configuration + super.init(presentedViewController: presentedVC, presenting: presentingVC) + } +} + +extension PresentationController { + override var frameOfPresentedViewInContainerView: CGRect { + var frame: CGRect = .zero + frame.size = size(forChildContentContainer: presentedViewController, + withParentContainerSize: containerViewSize) + frame.origin.y = drawerPartialY + return frame + } + + override func presentationTransitionWillBegin() { + containerView?.backgroundColor = .clear + setupContainerViewDismissalTapRecogniser() + setupPresentedViewDragRecogniser() + addCornerRadiusAnimationEnding(at: drawerPartialY) + } + + override func dismissalTransitionWillBegin() { + addCornerRadiusAnimationEnding(at: containerViewH) + } + + override func dismissalTransitionDidEnd(_ completed: Bool) { + removeContainerViewDismissalTapRecogniser() + removePresentedViewDragRecogniser() + } + + override func containerViewWillLayoutSubviews() { + presentedView?.frame = frameOfPresentedViewInContainerView + } +} + +private extension PresentationController { + var containerViewSize: CGSize { + return containerView?.bounds.size ?? .zero + } + + var containerViewH: CGFloat { + return containerViewSize.height + } + + var drawerPartialH: CGFloat { + guard let presentedVC = presentedViewController as? DrawerPresentable else { return 0 } + return presentedVC.heightOfPartiallyExpandedDrawer + } + + var drawerPartialY: CGFloat { + return containerViewH - drawerPartialH + } + + var upperMarkY: CGFloat { + return configuration.upperMarkFraction * (containerViewH - drawerPartialH) + } + + var lowerMarkY: CGFloat { + return drawerPartialY + configuration.lowerMarkFraction * drawerPartialH + } + + var currentDrawerY: CGFloat { + get { return presentedView?.frame.origin.y ?? 0 } + set { presentedView?.frame.origin.y = newValue } + } + + var currentDrawerCornerRadius: CGFloat { + get { return presentedView?.layer.cornerRadius ?? 0 } + set { presentedView?.layer.cornerRadius = newValue } + } +} + +private extension PresentationController { + func setupContainerViewDismissalTapRecogniser() { + let gr = UITapGestureRecognizer(target: self, + action: #selector(handleContainerViewDismissalTap)) + containerView?.addGestureRecognizer(gr) + containerViewDismissalTapGR = gr + } + + func removeContainerViewDismissalTapRecogniser() { + guard let gr = containerViewDismissalTapGR else { return } + containerView?.removeGestureRecognizer(gr) + containerViewDismissalTapGR = nil + } + + @objc func handleContainerViewDismissalTap() { + guard let gr = containerViewDismissalTapGR else { return } + let tapY = gr.location(in: containerView).y + guard tapY < currentDrawerY else { return } + presentedViewController.dismiss(animated: true) + } +} + +private extension PresentationController { + func setupPresentedViewDragRecogniser() { + let gr = UIPanGestureRecognizer(target: self, + action: #selector(handlePresentedViewDrag)) + presentedView?.addGestureRecognizer(gr) + presentedViewDragGR = gr + } + + func removePresentedViewDragRecogniser() { + guard let gr = presentedViewDragGR else { return } + presentedView?.removeGestureRecognizer(gr) + presentedViewDragGR = nil + } + + @objc func handlePresentedViewDrag() { + guard let gr = presentedViewDragGR, let view = gr.view else { return } + + let offsetY = gr.translation(in: view).y + gr.setTranslation(.zero, in: view) + + switch gr.state { + case .began: + lastDrawerY = currentDrawerY // XXX + + case .changed: + lastDrawerY = currentDrawerY // XXX + let positionY = currentDrawerY + offsetY + currentDrawerY = min(max(positionY, 0), containerViewH) + currentDrawerCornerRadius = cornerRadius(at: currentDrawerY) + + case .ended: + let drawerVelocityY = gr.velocity(in: view).y / containerViewH + let endPosY = endingPositionY(positionY: currentDrawerY, + velocityY: drawerVelocityY) + animateTransition(to: endPosY) + + case .cancelled: + animateTransition(to: lastDrawerY) + + default: + break + } + } +} + +private extension PresentationController { + func animateTransition(to endPositionY: CGFloat) { + addPositionAnimationEnding(at: endPositionY) + addCornerRadiusAnimationEnding(at: endPositionY) + } + + func addPositionAnimationEnding(at endPositionY: CGFloat) { + guard endPositionY != currentDrawerY else { return } + + let animator = makeAnimator(to: endPositionY) + + animator.addAnimations { [weak self] in + self?.currentDrawerY = endPositionY + } + + if endPositionY == containerViewH { + animator.addCompletion { [weak self] _ in + self?.presentedViewController.dismiss(animated: true) + } + } + + animator.startAnimation() + } + + func addCornerRadiusAnimationEnding(at endPositionY: CGFloat) { + guard drawerPartialY > 0 else { return } + guard endPositionY != currentDrawerY else { return } + + let animator = makeAnimator(to: endPositionY) + + let endingCornerRadius = cornerRadius(at: endPositionY) + animator.addAnimations { [weak self] in + self?.currentDrawerCornerRadius = endingCornerRadius + } + + animator.startAnimation() + } + + func makeAnimator(to endPositionY: CGFloat) -> UIViewPropertyAnimator { + let timingConfiguration: TimingConfiguration + if endPositionY == drawerPartialY { + timingConfiguration = configuration.partialTransitionTimingConfiguration + } else { + timingConfiguration = configuration.fullTransitionTimingConfiguration + } + + let duration = timingConfiguration.durationInSeconds + let timingParams = timingConfiguration.timingCurveProvider + return UIViewPropertyAnimator(duration: duration, + timingParameters: timingParams) + } + + func cornerRadius(at positionY: CGFloat) -> CGFloat { + let maxCornerRadius = configuration.maximumCornerRadius + guard drawerPartialY > 0 else { return 0 } + let fraction: CGFloat + if positionY <= drawerPartialY { + fraction = positionY / drawerPartialY + } else { + fraction = 1 - (positionY - drawerPartialY) / (containerViewH - drawerPartialY) + } + return fraction * maxCornerRadius + } +} + +private extension PresentationController { + func endingPositionY(positionY: CGFloat, velocityY: CGFloat) -> CGFloat { + let velocityThresholdY = configuration.flickSpeedThreshold + let allowPartialExpansion = configuration.supportsPartialExpansion + let stagedDismissal = configuration.dismissesInStages + return endingPositionY(positionY: positionY, + upperMarkY: upperMarkY, + lowerMarkY: lowerMarkY, + velocityY: velocityY, + velocityThresholdY: velocityThresholdY, + allowPartialExpansion: allowPartialExpansion, + stagedDismissal: stagedDismissal) + } + + func endingPositionY(positionY: CGFloat, upperMarkY: CGFloat, lowerMarkY: CGFloat, + velocityY: CGFloat, velocityThresholdY: CGFloat, + allowPartialExpansion: Bool, stagedDismissal: Bool) -> CGFloat { + let isMovingUp = (velocityY < 0) // recall that Y-axis points down + let isMovingDown = !isMovingUp + let isMovingQuickly = (abs(velocityY) > velocityThresholdY) + let isMovingUpQuickly = isMovingUp && isMovingQuickly + let isMovingDownQuickly = isMovingDown && isMovingQuickly + let isAboveUpperMark = (positionY < upperMarkY) + let isAboveLowerMark = (positionY < lowerMarkY) + + guard !isMovingUpQuickly else { return 0 } + guard !isMovingDownQuickly else { return containerViewH } + + guard !isAboveUpperMark else { + if isMovingUp { + return 0 + } else { + return stagedDismissal ? drawerPartialY : containerViewH + } + } + + guard !isAboveLowerMark else { + if isMovingDown { + return containerViewH + } else { + return (allowPartialExpansion ? drawerPartialY : 0) + } + } + + return containerViewH + } +} diff --git a/DrawerKit/DrawerKit/Internal API/TransitionAnimator.swift b/DrawerKit/DrawerKit/Internal API/TransitionAnimator.swift new file mode 100644 index 0000000..d7ada18 --- /dev/null +++ b/DrawerKit/DrawerKit/Internal API/TransitionAnimator.swift @@ -0,0 +1,55 @@ +import UIKit + +final class TransitionAnimator: NSObject { + private let configuration: DrawerConfiguration // intentionally immutable + private let isPresentation: Bool + private var isFirstRun = true + private var timingConfiguration: TimingConfiguration + + init(isPresentation: Bool, configuration: DrawerConfiguration) { + self.configuration = configuration + self.isPresentation = isPresentation + self.timingConfiguration = configuration.partialTransitionTimingConfiguration + super.init() + } +} + +extension TransitionAnimator: UIViewControllerAnimatedTransitioning { + + func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { + return timingConfiguration.durationInSeconds + } + + func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + let key = (isPresentation ? + UITransitionContextViewControllerKey.to : + UITransitionContextViewControllerKey.from) + + let controller = transitionContext.viewController(forKey: key)! + if isPresentation { transitionContext.containerView.addSubview(controller.view) } + + let presentedFrame = transitionContext.finalFrame(for: controller) + var dismissedFrame = presentedFrame + dismissedFrame.origin.y = transitionContext.containerView.frame.size.height + + let initialFrame = (isPresentation ? dismissedFrame : presentedFrame) + let finalFrame = (isPresentation ? presentedFrame : dismissedFrame) + + let duration = timingConfiguration.durationInSeconds + let timingParams = timingConfiguration.timingCurveProvider + let animator = UIViewPropertyAnimator(duration: duration, + timingParameters: timingParams) + + timingConfiguration = configuration.fullTransitionTimingConfiguration + + controller.view.frame = initialFrame + animator.addAnimations { controller.view.frame = finalFrame } + + animator.addCompletion { endingPosition in + let finished = (endingPosition == UIViewAnimatingPosition.end) + transitionContext.completeTransition(finished) + } + + animator.startAnimation() + } +} diff --git a/DrawerKit/DrawerKit/Internal API/TransitionGeometry.swift b/DrawerKit/DrawerKit/Internal API/TransitionGeometry.swift new file mode 100644 index 0000000..cecfd55 --- /dev/null +++ b/DrawerKit/DrawerKit/Internal API/TransitionGeometry.swift @@ -0,0 +1,27 @@ +import UIKit + +struct TransitionGeometry { + var userInterfaceOrientation: UIInterfaceOrientation + var actualStatusBarHeight: CGFloat + var navigationBarHeight: CGFloat + var heightOfPartiallyExpandedDrawer: CGFloat + + init(userInterfaceOrientation: UIInterfaceOrientation, + actualStatusBarHeight: CGFloat, + navigationBarHeight: CGFloat, + heightOfPartiallyExpandedDrawer: CGFloat) { + self.userInterfaceOrientation = userInterfaceOrientation + self.actualStatusBarHeight = actualStatusBarHeight + self.navigationBarHeight = navigationBarHeight + self.heightOfPartiallyExpandedDrawer = heightOfPartiallyExpandedDrawer + } +} + +extension TransitionGeometry: Equatable { + static func ==(lhs: TransitionGeometry, rhs: TransitionGeometry) -> Bool { + return lhs.userInterfaceOrientation == rhs.userInterfaceOrientation + && lhs.actualStatusBarHeight == rhs.actualStatusBarHeight + && lhs.navigationBarHeight == rhs.navigationBarHeight + && lhs.heightOfPartiallyExpandedDrawer == rhs.heightOfPartiallyExpandedDrawer + } +} diff --git a/DrawerKit/DrawerKit/Public API/DrawerDisplayController+Extensions.swift b/DrawerKit/DrawerKit/Public API/DrawerDisplayController+Extensions.swift new file mode 100755 index 0000000..a7eb129 --- /dev/null +++ b/DrawerKit/DrawerKit/Public API/DrawerDisplayController+Extensions.swift @@ -0,0 +1,53 @@ +import UIKit + +/// A collection of convenience getter functions to access the drawer +/// configuration parameters directly from the drawer display controller. +extension DrawerDisplayController { + public var fullTransitionTimingConfiguration: TimingConfiguration { + return configuration.fullTransitionTimingConfiguration + } + + public var partialTransitionTimingConfiguration: TimingConfiguration { + return configuration.partialTransitionTimingConfiguration + } + + public var coversStatusBar: Bool { + return configuration.coversStatusBar + } + + public var supportsPartialExpansion: Bool { + return configuration.supportsPartialExpansion + } + + public var dismissesInStages: Bool { + return configuration.dismissesInStages + } + + public var isDrawerDraggable: Bool { + return configuration.isDrawerDraggable + } + + public var isDismissableByOutsideDrawerTaps: Bool { + return configuration.isDismissableByOutsideDrawerTaps + } + + public var numberOfTapsForOutsideDrawerDismissal: Int { + return configuration.numberOfTapsForOutsideDrawerDismissal + } + + public var flickSpeedThreshold: CGFloat { + return configuration.flickSpeedThreshold + } + + public var upperMarkFraction: CGFloat { + return configuration.upperMarkFraction + } + + public var lowerMarkFraction: CGFloat { + return configuration.lowerMarkFraction + } + + public var maximumCornerRadius: CGFloat { + return configuration.maximumCornerRadius + } +} diff --git a/DrawerKit/DrawerKit/Public API/DrawerDisplayController.swift b/DrawerKit/DrawerKit/Public API/DrawerDisplayController.swift new file mode 100755 index 0000000..066b68e --- /dev/null +++ b/DrawerKit/DrawerKit/Public API/DrawerDisplayController.swift @@ -0,0 +1,17 @@ +import UIKit + +public final class DrawerDisplayController: NSObject { + public let configuration: DrawerConfiguration // intentionally immutable + + weak var presentingVC: (UIViewController & DrawerPresenting)? + /* strong */ var presentedVC: (UIViewController & DrawerPresentable) + + init(presentingViewController: (UIViewController & DrawerPresenting), + presentedViewController: (UIViewController & DrawerPresentable), + configuration: DrawerConfiguration = DrawerConfiguration()) { + self.presentingVC = presentingViewController + self.presentedVC = presentedViewController + self.configuration = configuration + super.init() + } +} diff --git a/DrawerKit/DrawerKit/Public API/Protocols/DrawerPresentable.swift b/DrawerKit/DrawerKit/Public API/Protocols/DrawerPresentable.swift new file mode 100644 index 0000000..fa9fb07 --- /dev/null +++ b/DrawerKit/DrawerKit/Public API/Protocols/DrawerPresentable.swift @@ -0,0 +1,12 @@ +import UIKit + +/// Protocol that view controllers presented inside a drawer must conform to. +public protocol DrawerPresentable: class { + var heightOfPartiallyExpandedDrawer: CGFloat { get } +} + +public extension DrawerPresentable where Self: UIViewController { + public var heightOfPartiallyExpandedDrawer: CGFloat { + return 0 + } +} diff --git a/DrawerKit/DrawerKit/Public API/Protocols/DrawerPresenting.swift b/DrawerKit/DrawerKit/Public API/Protocols/DrawerPresenting.swift new file mode 100644 index 0000000..207d174 --- /dev/null +++ b/DrawerKit/DrawerKit/Public API/Protocols/DrawerPresenting.swift @@ -0,0 +1,6 @@ +import UIKit + +/// Protocol that view controllers presenting a drawer must conform to. +public protocol DrawerPresenting: class { + var drawerDisplayController: DrawerDisplayController? { get } +} diff --git a/DrawerKit/DrawerKit/Public API/Structs/DrawerConfiguration.swift b/DrawerKit/DrawerKit/Public API/Structs/DrawerConfiguration.swift new file mode 100644 index 0000000..f47b64c --- /dev/null +++ b/DrawerKit/DrawerKit/Public API/Structs/DrawerConfiguration.swift @@ -0,0 +1,48 @@ +import UIKit + +/// All the configurable parameters in one place. +public struct DrawerConfiguration: Equatable { + public var fullTransitionTimingConfiguration: TimingConfiguration + public var partialTransitionTimingConfiguration: TimingConfiguration + + public var coversStatusBar: Bool + public var supportsPartialExpansion: Bool + public var dismissesInStages: Bool + public var isDrawerDraggable: Bool + + public var isDismissableByOutsideDrawerTaps: Bool + public var numberOfTapsForOutsideDrawerDismissal: Int + + public var flickSpeedThreshold: CGFloat + + public var upperMarkFraction: CGFloat + public var lowerMarkFraction: CGFloat + + public var maximumCornerRadius: CGFloat + + public init(fullTransitionTimingConfiguration: TimingConfiguration = TimingConfiguration(), + partialTransitionTimingConfiguration: TimingConfiguration = TimingConfiguration(), + coversStatusBar: Bool = true, + supportsPartialExpansion: Bool = true, + dismissesInStages: Bool = true, + isDrawerDraggable: Bool = true, + isDismissableByOutsideDrawerTaps: Bool = true, + numberOfTapsForOutsideDrawerDismissal: Int = 1, + flickSpeedThreshold: CGFloat = 3, + upperMarkFraction: CGFloat = 0.5, + lowerMarkFraction: CGFloat = 0.5, + maximumCornerRadius: CGFloat = 15) { + self.fullTransitionTimingConfiguration = fullTransitionTimingConfiguration + self.partialTransitionTimingConfiguration = partialTransitionTimingConfiguration + self.coversStatusBar = coversStatusBar + self.supportsPartialExpansion = supportsPartialExpansion + self.dismissesInStages = dismissesInStages + self.isDrawerDraggable = isDrawerDraggable + self.isDismissableByOutsideDrawerTaps = isDismissableByOutsideDrawerTaps + self.numberOfTapsForOutsideDrawerDismissal = numberOfTapsForOutsideDrawerDismissal + self.flickSpeedThreshold = flickSpeedThreshold + self.upperMarkFraction = upperMarkFraction + self.lowerMarkFraction = lowerMarkFraction + self.maximumCornerRadius = maximumCornerRadius + } +} diff --git a/DrawerKit/DrawerKit/Public API/Structs/TimingConfiguration.swift b/DrawerKit/DrawerKit/Public API/Structs/TimingConfiguration.swift new file mode 100644 index 0000000..5a8a72d --- /dev/null +++ b/DrawerKit/DrawerKit/Public API/Structs/TimingConfiguration.swift @@ -0,0 +1,12 @@ +import UIKit + +public struct TimingConfiguration: Equatable { + public var durationInSeconds: TimeInterval + public var timingCurveProvider: UITimingCurveProvider + + public init(durationInSeconds: TimeInterval = 0.8, + timingCurveProvider: UITimingCurveProvider = UISpringTimingParameters()) { + self.durationInSeconds = durationInSeconds + self.timingCurveProvider = timingCurveProvider + } +} From 752abad3d4671bca02b9c1ad98feec8e8a451355 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Wed, 11 Oct 2017 23:43:42 +0100 Subject: [PATCH 08/85] Hooked up the view controllers to the custom presentation. --- ...DisplayController+InternalExtensions.swift | 1 - .../Internal API/PresentationController.swift | 2 +- .../Public API/DrawerDisplayController.swift | 9 ++++-- .../PresentedViewController.swift | 13 ++++++++ .../PresenterViewController.swift | 30 +++++++++++++++++-- 5 files changed, 48 insertions(+), 7 deletions(-) diff --git a/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+InternalExtensions.swift b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+InternalExtensions.swift index 6253b61..18b00b1 100755 --- a/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+InternalExtensions.swift +++ b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+InternalExtensions.swift @@ -4,7 +4,6 @@ extension DrawerDisplayController: UIViewControllerTransitioningDelegate { public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { - guard let presentingVC = presenting else { return nil } let presentationController = PresentationController(presentingVC: presentingVC, presentedVC: presented, configuration: configuration) diff --git a/DrawerKit/DrawerKit/Internal API/PresentationController.swift b/DrawerKit/DrawerKit/Internal API/PresentationController.swift index 84cf09b..d5ad382 100644 --- a/DrawerKit/DrawerKit/Internal API/PresentationController.swift +++ b/DrawerKit/DrawerKit/Internal API/PresentationController.swift @@ -6,7 +6,7 @@ final class PresentationController: UIPresentationController { private var containerViewDismissalTapGR: UITapGestureRecognizer? private var presentedViewDragGR: UIPanGestureRecognizer? - init(presentingVC: UIViewController, presentedVC: UIViewController, + init(presentingVC: UIViewController?, presentedVC: UIViewController, configuration: DrawerConfiguration) { self.configuration = configuration super.init(presentedViewController: presentedVC, presenting: presentingVC) diff --git a/DrawerKit/DrawerKit/Public API/DrawerDisplayController.swift b/DrawerKit/DrawerKit/Public API/DrawerDisplayController.swift index 066b68e..d98c49e 100755 --- a/DrawerKit/DrawerKit/Public API/DrawerDisplayController.swift +++ b/DrawerKit/DrawerKit/Public API/DrawerDisplayController.swift @@ -6,12 +6,15 @@ public final class DrawerDisplayController: NSObject { weak var presentingVC: (UIViewController & DrawerPresenting)? /* strong */ var presentedVC: (UIViewController & DrawerPresentable) - init(presentingViewController: (UIViewController & DrawerPresenting), - presentedViewController: (UIViewController & DrawerPresentable), - configuration: DrawerConfiguration = DrawerConfiguration()) { + public init(presentingViewController: (UIViewController & DrawerPresenting), + presentedViewController: (UIViewController & DrawerPresentable), + configuration: DrawerConfiguration = DrawerConfiguration()) { self.presentingVC = presentingViewController self.presentedVC = presentedViewController self.configuration = configuration super.init() + presentedViewController.transitioningDelegate = self + presentedViewController.modalPresentationStyle = .custom + presentedViewController.modalTransitionStyle = .coverVertical } } diff --git a/DrawerKitDemo/DrawerKitDemo/PresentedViewController.swift b/DrawerKitDemo/DrawerKitDemo/PresentedViewController.swift index 94965c9..277410a 100644 --- a/DrawerKitDemo/DrawerKitDemo/PresentedViewController.swift +++ b/DrawerKitDemo/DrawerKitDemo/PresentedViewController.swift @@ -1,6 +1,10 @@ import UIKit import DrawerKit +// Search for the string 'THIS IS THE IMPORTANT PART' in both view controllers +// to see how to show the drawer. There may be more than one important part in +// each view controller. + class PresentedViewController: UIViewController { var hasFixedHeight = false @@ -13,3 +17,12 @@ class PresentedViewController: UIViewController { dismiss(animated: true) } } + +// ======== THIS IS THE IMPORTANT PART ======== // +extension PresentedViewController: DrawerPresentable { + var heightOfPartiallyExpandedDrawer: CGFloat { + guard let view = self.view as? PresentedView else { return 0 } + return view.dividerView.frame.origin.y + } +} +// ============================================ // diff --git a/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift b/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift index 32b0cf2..47a8b68 100644 --- a/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift +++ b/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift @@ -1,8 +1,14 @@ import UIKit import DrawerKit -class PresenterViewController: UIViewController { - +// Search for the string 'THIS IS THE IMPORTANT PART' in both view controllers +// to see how to show the drawer. There may be more than one important part in +// each view controller. + +// ======== THIS IS THE IMPORTANT PART ======== // +class PresenterViewController: UIViewController, DrawerPresenting { + /* strong */ var drawerDisplayController: DrawerDisplayController? + // ============================================ // private static let defaultDuration: Float = 0.8 private var _duration: TimeInterval = 0 private var duration: Float { @@ -52,6 +58,25 @@ extension PresenterViewController { guard let vc = sb.instantiateViewController(withIdentifier: "presented") as? PresentedViewController else { return } vc.hasFixedHeight = hasFixedHeight + + // ======== THIS IS THE IMPORTANT PART ======== // + // you can provide the configuration values in the initialiser... + var configuration = DrawerConfiguration(/* ..., ..., ..., */) + + // ... or after initialisation + // configuration.totalDurationInSeconds = _duration // XXX + // configuration.timingCurveProvider = UICubicTimingParameters(animationCurve: .easeInOut) + configuration.coversStatusBar = coversStatusBar + configuration.supportsPartialExpansion = supportsPartialExpansion + configuration.dismissesInStages = dismissesInStages + configuration.isDrawerDraggable = isDrawerDraggable + configuration.isDismissableByOutsideDrawerTaps = isDismissableByOutsideDrawerTaps + + drawerDisplayController = DrawerDisplayController(presentingViewController: self, + presentedViewController: vc, + configuration: configuration) + // ============================================ // + present(vc, animated: true) } } @@ -166,3 +191,4 @@ private extension PresenterViewController { return Float(truncf(10 * value)) / 10 } } + From 3c0b91cce66f198a13e8810717e2dc41d232e4fb Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Thu, 12 Oct 2017 00:06:38 +0100 Subject: [PATCH 09/85] Added clamping to the three possible resting positions of the drawer --- .../Internal API/PresentationController.swift | 42 +++++++++++++------ 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/DrawerKit/DrawerKit/Internal API/PresentationController.swift b/DrawerKit/DrawerKit/Internal API/PresentationController.swift index d5ad382..4ebb7b4 100644 --- a/DrawerKit/DrawerKit/Internal API/PresentationController.swift +++ b/DrawerKit/DrawerKit/Internal API/PresentationController.swift @@ -124,10 +124,10 @@ private extension PresentationController { switch gr.state { case .began: - lastDrawerY = currentDrawerY // XXX + lastDrawerY = currentDrawerY case .changed: - lastDrawerY = currentDrawerY // XXX + lastDrawerY = currentDrawerY let positionY = currentDrawerY + offsetY currentDrawerY = min(max(positionY, 0), containerViewH) currentDrawerCornerRadius = cornerRadius(at: currentDrawerY) @@ -139,7 +139,7 @@ private extension PresentationController { animateTransition(to: endPosY) case .cancelled: - animateTransition(to: lastDrawerY) + animateTransition(to: lastDrawerY, clamping: true) default: break @@ -148,21 +148,24 @@ private extension PresentationController { } private extension PresentationController { - func animateTransition(to endPositionY: CGFloat) { - addPositionAnimationEnding(at: endPositionY) - addCornerRadiusAnimationEnding(at: endPositionY) + func animateTransition(to endPositionY: CGFloat, clamping: Bool = false) { + addPositionAnimationEnding(at: endPositionY, clamping: clamping) + addCornerRadiusAnimationEnding(at: endPositionY, clamping: clamping) } - func addPositionAnimationEnding(at endPositionY: CGFloat) { + func addPositionAnimationEnding(at endPositionY: CGFloat, clamping: Bool = false) { guard endPositionY != currentDrawerY else { return } - let animator = makeAnimator(to: endPositionY) + let endPosY = (clamping ? clamped(endPositionY) : endPositionY) + guard endPosY != currentDrawerY else { return } + + let animator = makeAnimator(to: endPosY) animator.addAnimations { [weak self] in - self?.currentDrawerY = endPositionY + self?.currentDrawerY = endPosY } - if endPositionY == containerViewH { + if endPosY == containerViewH { animator.addCompletion { [weak self] _ in self?.presentedViewController.dismiss(animated: true) } @@ -171,13 +174,16 @@ private extension PresentationController { animator.startAnimation() } - func addCornerRadiusAnimationEnding(at endPositionY: CGFloat) { + func addCornerRadiusAnimationEnding(at endPositionY: CGFloat, clamping: Bool = false) { guard drawerPartialY > 0 else { return } guard endPositionY != currentDrawerY else { return } - let animator = makeAnimator(to: endPositionY) + let endPosY = (clamping ? clamped(endPositionY) : endPositionY) + guard endPosY != currentDrawerY else { return } + + let animator = makeAnimator(to: endPosY) - let endingCornerRadius = cornerRadius(at: endPositionY) + let endingCornerRadius = cornerRadius(at: endPosY) animator.addAnimations { [weak self] in self?.currentDrawerCornerRadius = endingCornerRadius } @@ -258,4 +264,14 @@ private extension PresentationController { return containerViewH } + + func clamped(_ positionY: CGFloat) -> CGFloat { + if positionY < upperMarkY { + return 0 + } else if positionY > lowerMarkY { + return containerViewH + } else { + return drawerPartialY + } + } } From 878ca27fd71a361af4111153a4e75471345e8b16 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Thu, 12 Oct 2017 00:06:54 +0100 Subject: [PATCH 10/85] Added more configuration to the presenting VC --- .../DrawerKitDemo/PresenterViewController.swift | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift b/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift index 47a8b68..fcb6f89 100644 --- a/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift +++ b/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift @@ -64,13 +64,24 @@ extension PresenterViewController { var configuration = DrawerConfiguration(/* ..., ..., ..., */) // ... or after initialisation - // configuration.totalDurationInSeconds = _duration // XXX - // configuration.timingCurveProvider = UICubicTimingParameters(animationCurve: .easeInOut) + let partialTimingParams = + TimingConfiguration(durationInSeconds: 0.8, + timingCurveProvider: UISpringTimingParameters(dampingRatio: 0.7)) + let fullTimingParams = + TimingConfiguration(durationInSeconds: 0.8, + timingCurveProvider: UISpringTimingParameters(dampingRatio: 0.2)) + configuration.partialTransitionTimingConfiguration = partialTimingParams + configuration.fullTransitionTimingConfiguration = fullTimingParams + configuration.coversStatusBar = coversStatusBar configuration.supportsPartialExpansion = supportsPartialExpansion configuration.dismissesInStages = dismissesInStages configuration.isDrawerDraggable = isDrawerDraggable configuration.isDismissableByOutsideDrawerTaps = isDismissableByOutsideDrawerTaps +// configuration.flickSpeedThreshold = flickSpeedThreshold // XXX +// configuration.upperMarkFraction = upperMarkFraction +// configuration.lowerMarkFraction = lowerMarkFraction +// configuration.maximumCornerRadius = maximumCornerRadius drawerDisplayController = DrawerDisplayController(presentingViewController: self, presentedViewController: vc, @@ -191,4 +202,3 @@ private extension PresenterViewController { return Float(truncf(10 * value)) / 10 } } - From 0255cbe81d11e26311a6c0bc2d61da324522080c Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Thu, 12 Oct 2017 00:30:13 +0100 Subject: [PATCH 11/85] Fixed a few glitches related to allowing partial expansion or not, and some refactoring. --- .../Internal API/PresentationController.swift | 74 ++++++++++++------- 1 file changed, 46 insertions(+), 28 deletions(-) diff --git a/DrawerKit/DrawerKit/Internal API/PresentationController.swift b/DrawerKit/DrawerKit/Internal API/PresentationController.swift index 4ebb7b4..9bf3518 100644 --- a/DrawerKit/DrawerKit/Internal API/PresentationController.swift +++ b/DrawerKit/DrawerKit/Internal API/PresentationController.swift @@ -18,7 +18,7 @@ extension PresentationController { var frame: CGRect = .zero frame.size = size(forChildContentContainer: presentedViewController, withParentContainerSize: containerViewSize) - frame.origin.y = drawerPartialY + frame.origin.y = (supportsPartialExpansion ? drawerPartialY : 0) return frame } @@ -61,12 +61,44 @@ private extension PresentationController { return containerViewH - drawerPartialH } + var partialTransitionTimingConfiguration: TimingConfiguration { + return configuration.partialTransitionTimingConfiguration + } + + var fullTransitionTimingConfiguration: TimingConfiguration { + return configuration.fullTransitionTimingConfiguration + } + + var supportsPartialExpansion: Bool { + return configuration.supportsPartialExpansion + } + + var dismissesInStages: Bool { + return configuration.dismissesInStages + } + + var flickSpeedThreshold: CGFloat { + return configuration.flickSpeedThreshold + } + + var upperMarkFraction: CGFloat { + return configuration.upperMarkFraction + } + var upperMarkY: CGFloat { - return configuration.upperMarkFraction * (containerViewH - drawerPartialH) + return upperMarkFraction * (containerViewH - drawerPartialH) + } + + var lowerMarkFraction: CGFloat { + return configuration.lowerMarkFraction } var lowerMarkY: CGFloat { - return drawerPartialY + configuration.lowerMarkFraction * drawerPartialH + return drawerPartialY + lowerMarkFraction * drawerPartialH + } + + var maximumCornerRadius: CGFloat { + return configuration.maximumCornerRadius } var currentDrawerY: CGFloat { @@ -194,9 +226,9 @@ private extension PresentationController { func makeAnimator(to endPositionY: CGFloat) -> UIViewPropertyAnimator { let timingConfiguration: TimingConfiguration if endPositionY == drawerPartialY { - timingConfiguration = configuration.partialTransitionTimingConfiguration + timingConfiguration = partialTransitionTimingConfiguration } else { - timingConfiguration = configuration.fullTransitionTimingConfiguration + timingConfiguration = fullTransitionTimingConfiguration } let duration = timingConfiguration.durationInSeconds @@ -206,7 +238,6 @@ private extension PresentationController { } func cornerRadius(at positionY: CGFloat) -> CGFloat { - let maxCornerRadius = configuration.maximumCornerRadius guard drawerPartialY > 0 else { return 0 } let fraction: CGFloat if positionY <= drawerPartialY { @@ -214,30 +245,16 @@ private extension PresentationController { } else { fraction = 1 - (positionY - drawerPartialY) / (containerViewH - drawerPartialY) } - return fraction * maxCornerRadius + return fraction * maximumCornerRadius } } private extension PresentationController { func endingPositionY(positionY: CGFloat, velocityY: CGFloat) -> CGFloat { - let velocityThresholdY = configuration.flickSpeedThreshold - let allowPartialExpansion = configuration.supportsPartialExpansion - let stagedDismissal = configuration.dismissesInStages - return endingPositionY(positionY: positionY, - upperMarkY: upperMarkY, - lowerMarkY: lowerMarkY, - velocityY: velocityY, - velocityThresholdY: velocityThresholdY, - allowPartialExpansion: allowPartialExpansion, - stagedDismissal: stagedDismissal) - } - - func endingPositionY(positionY: CGFloat, upperMarkY: CGFloat, lowerMarkY: CGFloat, - velocityY: CGFloat, velocityThresholdY: CGFloat, - allowPartialExpansion: Bool, stagedDismissal: Bool) -> CGFloat { + let isNotMoving = (velocityY == 0) let isMovingUp = (velocityY < 0) // recall that Y-axis points down - let isMovingDown = !isMovingUp - let isMovingQuickly = (abs(velocityY) > velocityThresholdY) + let isMovingDown = (velocityY > 0) + let isMovingQuickly = (abs(velocityY) > flickSpeedThreshold) let isMovingUpQuickly = isMovingUp && isMovingQuickly let isMovingDownQuickly = isMovingDown && isMovingQuickly let isAboveUpperMark = (positionY < upperMarkY) @@ -247,10 +264,11 @@ private extension PresentationController { guard !isMovingDownQuickly else { return containerViewH } guard !isAboveUpperMark else { - if isMovingUp { + if isMovingUp || isNotMoving { return 0 } else { - return stagedDismissal ? drawerPartialY : containerViewH + let inStages = supportsPartialExpansion && dismissesInStages + return inStages ? drawerPartialY : containerViewH } } @@ -258,7 +276,7 @@ private extension PresentationController { if isMovingDown { return containerViewH } else { - return (allowPartialExpansion ? drawerPartialY : 0) + return (supportsPartialExpansion ? drawerPartialY : 0) } } @@ -271,7 +289,7 @@ private extension PresentationController { } else if positionY > lowerMarkY { return containerViewH } else { - return drawerPartialY + return (supportsPartialExpansion ? drawerPartialY : 0) } } } From 5ff1a8a950e1f69a09b18cb7b34e95ec86ba21f2 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Thu, 12 Oct 2017 00:47:50 +0100 Subject: [PATCH 12/85] Removed the two timing configurations and now using only one for all animations. --- DrawerKit/DrawerKit.xcodeproj/project.pbxproj | 30 ++--------------- .../TimingConfiguration+Extensions.swift | 8 ----- .../TransitionConfiguration+Extensions.swift | 4 +-- .../Internal API/PresentationController.swift | 32 +++++++------------ .../Internal API/TransitionAnimator.swift | 11 ++----- .../{Structs => }/DrawerConfiguration.swift | 12 +++---- .../DrawerDisplayController+Extensions.swift | 8 ++--- .../{Protocols => }/DrawerPresentable.swift | 0 .../{Protocols => }/DrawerPresenting.swift | 0 .../Structs/TimingConfiguration.swift | 12 ------- .../PresenterViewController.swift | 11 ++----- 11 files changed, 32 insertions(+), 96 deletions(-) delete mode 100644 DrawerKit/DrawerKit/Internal API/Extensions/TimingConfiguration+Extensions.swift rename DrawerKit/DrawerKit/Public API/{Structs => }/DrawerConfiguration.swift (76%) rename DrawerKit/DrawerKit/Public API/{Protocols => }/DrawerPresentable.swift (100%) rename DrawerKit/DrawerKit/Public API/{Protocols => }/DrawerPresenting.swift (100%) delete mode 100644 DrawerKit/DrawerKit/Public API/Structs/TimingConfiguration.swift diff --git a/DrawerKit/DrawerKit.xcodeproj/project.pbxproj b/DrawerKit/DrawerKit.xcodeproj/project.pbxproj index 3d68a5d..1bf5028 100644 --- a/DrawerKit/DrawerKit.xcodeproj/project.pbxproj +++ b/DrawerKit/DrawerKit.xcodeproj/project.pbxproj @@ -13,8 +13,6 @@ CB2CB79D1F8E951900AA152D /* DrawerDisplayController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB79A1F8E951900AA152D /* DrawerDisplayController.swift */; }; CB2CB79E1F8E951900AA152D /* PresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB79B1F8E951900AA152D /* PresentationController.swift */; }; CB2CB79F1F8E951900AA152D /* TransitionAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB79C1F8E951900AA152D /* TransitionAnimator.swift */; }; - CB2CB7A31F8E966E00AA152D /* TimingConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7A21F8E966D00AA152D /* TimingConfiguration.swift */; }; - CB2CB7A91F8E9AC000AA152D /* TimingConfiguration+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7A51F8E98F100AA152D /* TimingConfiguration+Extensions.swift */; }; CB2CB7AA1F8E9AC600AA152D /* TransitionConfiguration+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7A61F8E98F100AA152D /* TransitionConfiguration+Extensions.swift */; }; CB2CB7AB1F8E9BB400AA152D /* TransitionGeometry.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7921F8E934500AA152D /* TransitionGeometry.swift */; }; CB2CB7AD1F8E9D9900AA152D /* DrawerDisplayController+InternalExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7AC1F8E9D9900AA152D /* DrawerDisplayController+InternalExtensions.swift */; }; @@ -30,8 +28,6 @@ CB2CB79A1F8E951900AA152D /* DrawerDisplayController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DrawerDisplayController.swift; sourceTree = ""; }; CB2CB79B1F8E951900AA152D /* PresentationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentationController.swift; sourceTree = ""; }; CB2CB79C1F8E951900AA152D /* TransitionAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransitionAnimator.swift; sourceTree = ""; }; - CB2CB7A21F8E966D00AA152D /* TimingConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimingConfiguration.swift; sourceTree = ""; }; - CB2CB7A51F8E98F100AA152D /* TimingConfiguration+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimingConfiguration+Extensions.swift"; sourceTree = ""; }; CB2CB7A61F8E98F100AA152D /* TransitionConfiguration+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TransitionConfiguration+Extensions.swift"; sourceTree = ""; }; CB2CB7AC1F8E9D9900AA152D /* DrawerDisplayController+InternalExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DrawerDisplayController+InternalExtensions.swift"; sourceTree = ""; }; CB2CB7AE1F8E9F4C00AA152D /* DrawerDisplayController+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DrawerDisplayController+Extensions.swift"; sourceTree = ""; }; @@ -54,10 +50,11 @@ CB2CB7A01F8E962100AA152D /* Public API */ = { isa = PBXGroup; children = ( + CB2CB78E1F8E934500AA152D /* DrawerPresenting.swift */, + CB2CB78D1F8E934500AA152D /* DrawerPresentable.swift */, CB2CB79A1F8E951900AA152D /* DrawerDisplayController.swift */, CB2CB7AE1F8E9F4C00AA152D /* DrawerDisplayController+Extensions.swift */, - CB2CB7A71F8E9A8900AA152D /* Protocols */, - CB2CB7A81F8E9AA000AA152D /* Structs */, + CB2CB7911F8E934500AA152D /* DrawerConfiguration.swift */, ); path = "Public API"; sourceTree = ""; @@ -78,29 +75,10 @@ children = ( CB2CB7AC1F8E9D9900AA152D /* DrawerDisplayController+InternalExtensions.swift */, CB2CB7A61F8E98F100AA152D /* TransitionConfiguration+Extensions.swift */, - CB2CB7A51F8E98F100AA152D /* TimingConfiguration+Extensions.swift */, ); path = Extensions; sourceTree = ""; }; - CB2CB7A71F8E9A8900AA152D /* Protocols */ = { - isa = PBXGroup; - children = ( - CB2CB78E1F8E934500AA152D /* DrawerPresenting.swift */, - CB2CB78D1F8E934500AA152D /* DrawerPresentable.swift */, - ); - path = Protocols; - sourceTree = ""; - }; - CB2CB7A81F8E9AA000AA152D /* Structs */ = { - isa = PBXGroup; - children = ( - CB2CB7911F8E934500AA152D /* DrawerConfiguration.swift */, - CB2CB7A21F8E966D00AA152D /* TimingConfiguration.swift */, - ); - path = Structs; - sourceTree = ""; - }; CBBA2D4D1F8E815B00E0137F = { isa = PBXGroup; children = ( @@ -210,7 +188,6 @@ files = ( CB2CB7941F8E934500AA152D /* DrawerPresentable.swift in Sources */, CB2CB7AD1F8E9D9900AA152D /* DrawerDisplayController+InternalExtensions.swift in Sources */, - CB2CB7A91F8E9AC000AA152D /* TimingConfiguration+Extensions.swift in Sources */, CB2CB79F1F8E951900AA152D /* TransitionAnimator.swift in Sources */, CB2CB7951F8E934500AA152D /* DrawerPresenting.swift in Sources */, CB2CB7AF1F8E9F4C00AA152D /* DrawerDisplayController+Extensions.swift in Sources */, @@ -219,7 +196,6 @@ CB2CB7AA1F8E9AC600AA152D /* TransitionConfiguration+Extensions.swift in Sources */, CB2CB79D1F8E951900AA152D /* DrawerDisplayController.swift in Sources */, CB2CB79E1F8E951900AA152D /* PresentationController.swift in Sources */, - CB2CB7A31F8E966E00AA152D /* TimingConfiguration.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/DrawerKit/DrawerKit/Internal API/Extensions/TimingConfiguration+Extensions.swift b/DrawerKit/DrawerKit/Internal API/Extensions/TimingConfiguration+Extensions.swift deleted file mode 100644 index 4f877cb..0000000 --- a/DrawerKit/DrawerKit/Internal API/Extensions/TimingConfiguration+Extensions.swift +++ /dev/null @@ -1,8 +0,0 @@ -import UIKit - -extension TimingConfiguration { - public static func ==(lhs: TimingConfiguration, rhs: TimingConfiguration) -> Bool { - return lhs.durationInSeconds == rhs.durationInSeconds - && lhs.timingCurveProvider === rhs.timingCurveProvider - } -} diff --git a/DrawerKit/DrawerKit/Internal API/Extensions/TransitionConfiguration+Extensions.swift b/DrawerKit/DrawerKit/Internal API/Extensions/TransitionConfiguration+Extensions.swift index e7cc3a2..fdf3332 100644 --- a/DrawerKit/DrawerKit/Internal API/Extensions/TransitionConfiguration+Extensions.swift +++ b/DrawerKit/DrawerKit/Internal API/Extensions/TransitionConfiguration+Extensions.swift @@ -2,8 +2,8 @@ import UIKit extension DrawerConfiguration { public static func ==(lhs: DrawerConfiguration, rhs: DrawerConfiguration) -> Bool { - return lhs.fullTransitionTimingConfiguration == rhs.fullTransitionTimingConfiguration - && lhs.partialTransitionTimingConfiguration == rhs.partialTransitionTimingConfiguration + return lhs.durationInSeconds == rhs.durationInSeconds + && lhs.timingCurveProvider === rhs.timingCurveProvider && lhs.coversStatusBar == rhs.coversStatusBar && lhs.supportsPartialExpansion == rhs.supportsPartialExpansion && lhs.dismissesInStages == rhs.dismissesInStages diff --git a/DrawerKit/DrawerKit/Internal API/PresentationController.swift b/DrawerKit/DrawerKit/Internal API/PresentationController.swift index 9bf3518..f51ede8 100644 --- a/DrawerKit/DrawerKit/Internal API/PresentationController.swift +++ b/DrawerKit/DrawerKit/Internal API/PresentationController.swift @@ -61,12 +61,12 @@ private extension PresentationController { return containerViewH - drawerPartialH } - var partialTransitionTimingConfiguration: TimingConfiguration { - return configuration.partialTransitionTimingConfiguration + var durationInSeconds: TimeInterval { + return configuration.durationInSeconds } - var fullTransitionTimingConfiguration: TimingConfiguration { - return configuration.fullTransitionTimingConfiguration + var timingCurveProvider: UITimingCurveProvider { + return configuration.timingCurveProvider } var supportsPartialExpansion: Bool { @@ -191,7 +191,10 @@ private extension PresentationController { let endPosY = (clamping ? clamped(endPositionY) : endPositionY) guard endPosY != currentDrawerY else { return } - let animator = makeAnimator(to: endPosY) + let duration = configuration.durationInSeconds + let timingParams = configuration.timingCurveProvider + let animator = UIViewPropertyAnimator(duration: duration, + timingParameters: timingParams) animator.addAnimations { [weak self] in self?.currentDrawerY = endPosY @@ -213,7 +216,10 @@ private extension PresentationController { let endPosY = (clamping ? clamped(endPositionY) : endPositionY) guard endPosY != currentDrawerY else { return } - let animator = makeAnimator(to: endPosY) + let duration = configuration.durationInSeconds + let timingParams = configuration.timingCurveProvider + let animator = UIViewPropertyAnimator(duration: duration, + timingParameters: timingParams) let endingCornerRadius = cornerRadius(at: endPosY) animator.addAnimations { [weak self] in @@ -223,20 +229,6 @@ private extension PresentationController { animator.startAnimation() } - func makeAnimator(to endPositionY: CGFloat) -> UIViewPropertyAnimator { - let timingConfiguration: TimingConfiguration - if endPositionY == drawerPartialY { - timingConfiguration = partialTransitionTimingConfiguration - } else { - timingConfiguration = fullTransitionTimingConfiguration - } - - let duration = timingConfiguration.durationInSeconds - let timingParams = timingConfiguration.timingCurveProvider - return UIViewPropertyAnimator(duration: duration, - timingParameters: timingParams) - } - func cornerRadius(at positionY: CGFloat) -> CGFloat { guard drawerPartialY > 0 else { return 0 } let fraction: CGFloat diff --git a/DrawerKit/DrawerKit/Internal API/TransitionAnimator.swift b/DrawerKit/DrawerKit/Internal API/TransitionAnimator.swift index d7ada18..32c28ec 100644 --- a/DrawerKit/DrawerKit/Internal API/TransitionAnimator.swift +++ b/DrawerKit/DrawerKit/Internal API/TransitionAnimator.swift @@ -3,13 +3,10 @@ import UIKit final class TransitionAnimator: NSObject { private let configuration: DrawerConfiguration // intentionally immutable private let isPresentation: Bool - private var isFirstRun = true - private var timingConfiguration: TimingConfiguration init(isPresentation: Bool, configuration: DrawerConfiguration) { self.configuration = configuration self.isPresentation = isPresentation - self.timingConfiguration = configuration.partialTransitionTimingConfiguration super.init() } } @@ -17,7 +14,7 @@ final class TransitionAnimator: NSObject { extension TransitionAnimator: UIViewControllerAnimatedTransitioning { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { - return timingConfiguration.durationInSeconds + return configuration.durationInSeconds } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { @@ -35,13 +32,11 @@ extension TransitionAnimator: UIViewControllerAnimatedTransitioning { let initialFrame = (isPresentation ? dismissedFrame : presentedFrame) let finalFrame = (isPresentation ? presentedFrame : dismissedFrame) - let duration = timingConfiguration.durationInSeconds - let timingParams = timingConfiguration.timingCurveProvider + let duration = configuration.durationInSeconds + let timingParams = configuration.timingCurveProvider let animator = UIViewPropertyAnimator(duration: duration, timingParameters: timingParams) - timingConfiguration = configuration.fullTransitionTimingConfiguration - controller.view.frame = initialFrame animator.addAnimations { controller.view.frame = finalFrame } diff --git a/DrawerKit/DrawerKit/Public API/Structs/DrawerConfiguration.swift b/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift similarity index 76% rename from DrawerKit/DrawerKit/Public API/Structs/DrawerConfiguration.swift rename to DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift index f47b64c..8f03932 100644 --- a/DrawerKit/DrawerKit/Public API/Structs/DrawerConfiguration.swift +++ b/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift @@ -2,8 +2,8 @@ import UIKit /// All the configurable parameters in one place. public struct DrawerConfiguration: Equatable { - public var fullTransitionTimingConfiguration: TimingConfiguration - public var partialTransitionTimingConfiguration: TimingConfiguration + public var durationInSeconds: TimeInterval + public var timingCurveProvider: UITimingCurveProvider public var coversStatusBar: Bool public var supportsPartialExpansion: Bool @@ -20,8 +20,8 @@ public struct DrawerConfiguration: Equatable { public var maximumCornerRadius: CGFloat - public init(fullTransitionTimingConfiguration: TimingConfiguration = TimingConfiguration(), - partialTransitionTimingConfiguration: TimingConfiguration = TimingConfiguration(), + public init(durationInSeconds: TimeInterval = 0.8, + timingCurveProvider: UITimingCurveProvider = UISpringTimingParameters(), coversStatusBar: Bool = true, supportsPartialExpansion: Bool = true, dismissesInStages: Bool = true, @@ -32,8 +32,8 @@ public struct DrawerConfiguration: Equatable { upperMarkFraction: CGFloat = 0.5, lowerMarkFraction: CGFloat = 0.5, maximumCornerRadius: CGFloat = 15) { - self.fullTransitionTimingConfiguration = fullTransitionTimingConfiguration - self.partialTransitionTimingConfiguration = partialTransitionTimingConfiguration + self.durationInSeconds = durationInSeconds + self.timingCurveProvider = timingCurveProvider self.coversStatusBar = coversStatusBar self.supportsPartialExpansion = supportsPartialExpansion self.dismissesInStages = dismissesInStages diff --git a/DrawerKit/DrawerKit/Public API/DrawerDisplayController+Extensions.swift b/DrawerKit/DrawerKit/Public API/DrawerDisplayController+Extensions.swift index a7eb129..454e1a9 100755 --- a/DrawerKit/DrawerKit/Public API/DrawerDisplayController+Extensions.swift +++ b/DrawerKit/DrawerKit/Public API/DrawerDisplayController+Extensions.swift @@ -3,12 +3,12 @@ import UIKit /// A collection of convenience getter functions to access the drawer /// configuration parameters directly from the drawer display controller. extension DrawerDisplayController { - public var fullTransitionTimingConfiguration: TimingConfiguration { - return configuration.fullTransitionTimingConfiguration + public var durationInSeconds: TimeInterval { + return configuration.durationInSeconds } - public var partialTransitionTimingConfiguration: TimingConfiguration { - return configuration.partialTransitionTimingConfiguration + public var timingCurveProvider: UITimingCurveProvider { + return configuration.timingCurveProvider } public var coversStatusBar: Bool { diff --git a/DrawerKit/DrawerKit/Public API/Protocols/DrawerPresentable.swift b/DrawerKit/DrawerKit/Public API/DrawerPresentable.swift similarity index 100% rename from DrawerKit/DrawerKit/Public API/Protocols/DrawerPresentable.swift rename to DrawerKit/DrawerKit/Public API/DrawerPresentable.swift diff --git a/DrawerKit/DrawerKit/Public API/Protocols/DrawerPresenting.swift b/DrawerKit/DrawerKit/Public API/DrawerPresenting.swift similarity index 100% rename from DrawerKit/DrawerKit/Public API/Protocols/DrawerPresenting.swift rename to DrawerKit/DrawerKit/Public API/DrawerPresenting.swift diff --git a/DrawerKit/DrawerKit/Public API/Structs/TimingConfiguration.swift b/DrawerKit/DrawerKit/Public API/Structs/TimingConfiguration.swift deleted file mode 100644 index 5a8a72d..0000000 --- a/DrawerKit/DrawerKit/Public API/Structs/TimingConfiguration.swift +++ /dev/null @@ -1,12 +0,0 @@ -import UIKit - -public struct TimingConfiguration: Equatable { - public var durationInSeconds: TimeInterval - public var timingCurveProvider: UITimingCurveProvider - - public init(durationInSeconds: TimeInterval = 0.8, - timingCurveProvider: UITimingCurveProvider = UISpringTimingParameters()) { - self.durationInSeconds = durationInSeconds - self.timingCurveProvider = timingCurveProvider - } -} diff --git a/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift b/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift index fcb6f89..b203e00 100644 --- a/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift +++ b/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift @@ -64,15 +64,8 @@ extension PresenterViewController { var configuration = DrawerConfiguration(/* ..., ..., ..., */) // ... or after initialisation - let partialTimingParams = - TimingConfiguration(durationInSeconds: 0.8, - timingCurveProvider: UISpringTimingParameters(dampingRatio: 0.7)) - let fullTimingParams = - TimingConfiguration(durationInSeconds: 0.8, - timingCurveProvider: UISpringTimingParameters(dampingRatio: 0.2)) - configuration.partialTransitionTimingConfiguration = partialTimingParams - configuration.fullTransitionTimingConfiguration = fullTimingParams - + configuration.durationInSeconds = 0.8 + configuration.timingCurveProvider = UISpringTimingParameters(dampingRatio: 0.2) configuration.coversStatusBar = coversStatusBar configuration.supportsPartialExpansion = supportsPartialExpansion configuration.dismissesInStages = dismissesInStages From 8c83b607b1bd8071949f63753a3a8e1adf25bb6a Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Thu, 12 Oct 2017 01:02:10 +0100 Subject: [PATCH 13/85] Added support for not having the gesture recognisers. --- .../Internal API/PresentationController.swift | 20 +++++++++++++++++++ .../PresenterViewController.swift | 1 + 2 files changed, 21 insertions(+) diff --git a/DrawerKit/DrawerKit/Internal API/PresentationController.swift b/DrawerKit/DrawerKit/Internal API/PresentationController.swift index f51ede8..7f05248 100644 --- a/DrawerKit/DrawerKit/Internal API/PresentationController.swift +++ b/DrawerKit/DrawerKit/Internal API/PresentationController.swift @@ -101,6 +101,18 @@ private extension PresentationController { return configuration.maximumCornerRadius } + var isDrawerDraggable: Bool { + return configuration.isDrawerDraggable + } + + var isDismissableByOutsideDrawerTaps: Bool { + return configuration.isDismissableByOutsideDrawerTaps + } + + var numberOfTapsForOutsideDrawerDismissal: Int { + return configuration.numberOfTapsForOutsideDrawerDismissal + } + var currentDrawerY: CGFloat { get { return presentedView?.frame.origin.y ?? 0 } set { presentedView?.frame.origin.y = newValue } @@ -114,8 +126,14 @@ private extension PresentationController { private extension PresentationController { func setupContainerViewDismissalTapRecogniser() { + guard containerViewDismissalTapGR == nil else { return } + let isDismissable = isDismissableByOutsideDrawerTaps + let numTapsRequired = numberOfTapsForOutsideDrawerDismissal + guard isDismissable && numTapsRequired > 0 else { return } let gr = UITapGestureRecognizer(target: self, action: #selector(handleContainerViewDismissalTap)) + gr.numberOfTouchesRequired = 1 + gr.numberOfTapsRequired = numTapsRequired containerView?.addGestureRecognizer(gr) containerViewDismissalTapGR = gr } @@ -136,6 +154,8 @@ private extension PresentationController { private extension PresentationController { func setupPresentedViewDragRecogniser() { + guard presentedViewDragGR == nil else { return } + guard isDrawerDraggable else { return } let gr = UIPanGestureRecognizer(target: self, action: #selector(handlePresentedViewDrag)) presentedView?.addGestureRecognizer(gr) diff --git a/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift b/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift index b203e00..e7875b6 100644 --- a/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift +++ b/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift @@ -71,6 +71,7 @@ extension PresenterViewController { configuration.dismissesInStages = dismissesInStages configuration.isDrawerDraggable = isDrawerDraggable configuration.isDismissableByOutsideDrawerTaps = isDismissableByOutsideDrawerTaps + configuration.numberOfTapsForOutsideDrawerDismissal = numberOfTapsForOutsideDrawerDismissal // configuration.flickSpeedThreshold = flickSpeedThreshold // XXX // configuration.upperMarkFraction = upperMarkFraction // configuration.lowerMarkFraction = lowerMarkFraction From 55da51db8a2b53d8c255488f8bc58442d7e7c14c Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Thu, 12 Oct 2017 01:05:47 +0100 Subject: [PATCH 14/85] Fixed a potential division by zero. --- DrawerKit/DrawerKit/Internal API/PresentationController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DrawerKit/DrawerKit/Internal API/PresentationController.swift b/DrawerKit/DrawerKit/Internal API/PresentationController.swift index 7f05248..42a1122 100644 --- a/DrawerKit/DrawerKit/Internal API/PresentationController.swift +++ b/DrawerKit/DrawerKit/Internal API/PresentationController.swift @@ -252,7 +252,7 @@ private extension PresentationController { func cornerRadius(at positionY: CGFloat) -> CGFloat { guard drawerPartialY > 0 else { return 0 } let fraction: CGFloat - if positionY <= drawerPartialY { + if positionY < drawerPartialY { fraction = positionY / drawerPartialY } else { fraction = 1 - (positionY - drawerPartialY) / (containerViewH - drawerPartialY) From dbcf49e910a1acf0af197fb8f62bf727f8b43926 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Thu, 12 Oct 2017 02:56:23 +0100 Subject: [PATCH 15/85] Adding more configuration controls to presenting VC. WIP. --- .../DrawerKitDemo.xcodeproj/project.pbxproj | 14 + .../PresenterViewController.swift | 114 +++----- DrawerKitDemo/DrawerKitDemo/SliderView.swift | 117 +++++++++ .../Base.lproj/PresenterVC.storyboard | 247 +++++++----------- .../Storyboards/Base.lproj/SliderView.xib | 187 +++++++++++++ 5 files changed, 451 insertions(+), 228 deletions(-) create mode 100644 DrawerKitDemo/DrawerKitDemo/SliderView.swift create mode 100644 DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/SliderView.xib diff --git a/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj b/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj index f9c3882..75fb6d2 100644 --- a/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj +++ b/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + CB2CB7B71F8EEDF400AA152D /* SliderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7B61F8EEDF400AA152D /* SliderView.swift */; }; CBBA2D3A1F8E807400E0137F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBA2D391F8E807400E0137F /* AppDelegate.swift */; }; CBBA2D411F8E807400E0137F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CBBA2D3F1F8E807400E0137F /* Main.storyboard */; }; CBBA2D431F8E807400E0137F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CBBA2D421F8E807400E0137F /* Assets.xcassets */; }; @@ -36,6 +37,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + CB2CB7B51F8EEB2500AA152D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/SliderView.xib; sourceTree = ""; }; + CB2CB7B61F8EEDF400AA152D /* SliderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SliderView.swift; sourceTree = ""; }; CBBA2D361F8E807400E0137F /* DrawerKitDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DrawerKitDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; CBBA2D391F8E807400E0137F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; CBBA2D401F8E807400E0137F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; @@ -88,6 +91,7 @@ CBBA2D6C1F8E83E300E0137F /* PresenterView.swift */, CBBA2D6E1F8E83E300E0137F /* PresentedViewController.swift */, CBBA2D6D1F8E83E300E0137F /* PresentedView.swift */, + CB2CB7B61F8EEDF400AA152D /* SliderView.swift */, CBBA2D421F8E807400E0137F /* Assets.xcassets */, CBBA2D6A1F8E839200E0137F /* Storyboards */, CBBA2D471F8E807400E0137F /* Info.plist */, @@ -101,6 +105,7 @@ CBBA2D3F1F8E807400E0137F /* Main.storyboard */, CBBA2D661F8E836100E0137F /* PresenterVC.storyboard */, CBBA2D681F8E836100E0137F /* PresentedVC.storyboard */, + CB2CB7B41F8EEB2500AA152D /* SliderView.xib */, CBBA2D441F8E807400E0137F /* LaunchScreen.storyboard */, ); path = Storyboards; @@ -184,6 +189,7 @@ CBBA2D3A1F8E807400E0137F /* AppDelegate.swift in Sources */, CBBA2D721F8E83E300E0137F /* PresentedViewController.swift in Sources */, CBBA2D711F8E83E300E0137F /* PresentedView.swift in Sources */, + CB2CB7B71F8EEDF400AA152D /* SliderView.swift in Sources */, CBBA2D6F1F8E83E300E0137F /* PresenterViewController.swift in Sources */, CBBA2D701F8E83E300E0137F /* PresenterView.swift in Sources */, ); @@ -192,6 +198,14 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ + CB2CB7B41F8EEB2500AA152D /* SliderView.xib */ = { + isa = PBXVariantGroup; + children = ( + CB2CB7B51F8EEB2500AA152D /* Base */, + ); + name = SliderView.xib; + sourceTree = ""; + }; CBBA2D3F1F8E807400E0137F /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( diff --git a/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift b/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift index e7875b6..6e23c5a 100644 --- a/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift +++ b/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift @@ -9,17 +9,8 @@ import DrawerKit class PresenterViewController: UIViewController, DrawerPresenting { /* strong */ var drawerDisplayController: DrawerDisplayController? // ============================================ // - private static let defaultDuration: Float = 0.8 - private var _duration: TimeInterval = 0 - private var duration: Float { - get { return Float(_duration) } - set { - let currentDuration = Float(_duration) - guard newValue != currentDuration else { return } - set(duration: newValue) - } - } + private var durationInSeconds: CGFloat = 0.8 private var hasFixedHeight = false private var coversStatusBar = true private var supportsPartialExpansion = true @@ -27,6 +18,10 @@ class PresenterViewController: UIViewController, DrawerPresenting { private var isDrawerDraggable = true private var isDismissableByOutsideDrawerTaps = true private var numberOfTapsForOutsideDrawerDismissal: Int = 1 + private var flickSpeedThreshold: CGFloat = 3 + private var upperMarkFraction: CGFloat = 0.5 + private var lowerMarkFraction: CGFloat = 0.5 + private var maximumCornerRadius: CGFloat = 30 @IBOutlet weak var hasFixedHeightSwitch: UISwitch! @IBOutlet weak var coversStatusBarSwitch: UISwitch! @@ -34,22 +29,16 @@ class PresenterViewController: UIViewController, DrawerPresenting { @IBOutlet weak var dismissesInStagesSwitch: UISwitch! @IBOutlet weak var drawerDraggableSwitch: UISwitch! @IBOutlet weak var dismissableByOutsideTapButton: UIButton! - @IBOutlet weak var durationSlider: UISlider! - @IBOutlet weak var durationField: UITextField! + @IBOutlet weak var durationSliderView: SliderView! + @IBOutlet weak var flickSpeedThresholdSliderView: SliderView! + @IBOutlet weak var upperMarkFractionSliderView: SliderView! + @IBOutlet weak var lowerMarkFractionSliderView: SliderView! + @IBOutlet weak var maximumCornerRadiusSliderView: SliderView! override func viewDidLoad() { super.viewDidLoad() setup() } - - private static let formatter: NumberFormatter = { - let f = NumberFormatter() - f.allowsFloats = true - f.minimumIntegerDigits = 1 - f.minimumFractionDigits = 1 - f.maximumFractionDigits = 1 - return f - }() } extension PresenterViewController { @@ -64,18 +53,18 @@ extension PresenterViewController { var configuration = DrawerConfiguration(/* ..., ..., ..., */) // ... or after initialisation - configuration.durationInSeconds = 0.8 - configuration.timingCurveProvider = UISpringTimingParameters(dampingRatio: 0.2) + configuration.durationInSeconds = 0.8 // TimeInterval(durationSliderView.value) + configuration.timingCurveProvider = UISpringTimingParameters(dampingRatio: 0.7) configuration.coversStatusBar = coversStatusBar configuration.supportsPartialExpansion = supportsPartialExpansion configuration.dismissesInStages = dismissesInStages configuration.isDrawerDraggable = isDrawerDraggable configuration.isDismissableByOutsideDrawerTaps = isDismissableByOutsideDrawerTaps configuration.numberOfTapsForOutsideDrawerDismissal = numberOfTapsForOutsideDrawerDismissal -// configuration.flickSpeedThreshold = flickSpeedThreshold // XXX -// configuration.upperMarkFraction = upperMarkFraction -// configuration.lowerMarkFraction = lowerMarkFraction -// configuration.maximumCornerRadius = maximumCornerRadius + configuration.flickSpeedThreshold = 3 // flickSpeedThresholdSliderView.value.cgFloat + configuration.upperMarkFraction = 0.8 // upperMarkFractionSliderView.value.cgFloat + configuration.lowerMarkFraction = 0.5 // lowerMarkFractionSliderView.value.cgFloat + configuration.maximumCornerRadius = 15 // maximumCornerRadiusSliderView.value.cgFloat drawerDisplayController = DrawerDisplayController(presentingViewController: self, presentedViewController: vc, @@ -86,35 +75,11 @@ extension PresenterViewController { } } -extension PresenterViewController: UITextFieldDelegate { - func textFieldShouldReturn(_ textField: UITextField) -> Bool { - textField.resignFirstResponder() - return true - } - - func textFieldDidEndEditing(_ textField: UITextField) { - guard let duration = Float(textField.text ?? "0") else { - self.duration = PresenterViewController.defaultDuration - return - } - self.duration = duration - } -} - extension PresenterViewController { @IBAction func presentButtonTapped() { doModalPresentation() } - @IBAction func sliderSideButtonTapped(sender: UIButton) { - handleSliderSideButtonTapped(sender) - } - - @IBAction func durationSliderChanged() { - guard duration != durationSlider.value else { return } - duration = durationSlider.value - } - @IBAction func switchToggled(sender: UISwitch) { handleSwitchToggled(sender) } @@ -126,7 +91,21 @@ extension PresenterViewController { private extension PresenterViewController { func setup() { - duration = PresenterViewController.defaultDuration +// durationSliderView.configureWith( +// title: "Duration in secs", minValue: 0.3, maxValue: 5, +// initialValue: 0.8, defaultValue: 0.8) +// flickSpeedThresholdSliderView.configureWith( +// title: "Speed threshold", minValue: 1, maxValue: 5, +// initialValue: 3, defaultValue: 3) +// upperMarkFractionSliderView.configureWith( +// title: "Upper mark fraction", minValue: 0, maxValue: 1, +// initialValue: 0.8, defaultValue: 0.8) +// lowerMarkFractionSliderView.configureWith( +// title: "Lower mark fraction", minValue: 0, maxValue: 1, +// initialValue: 0.5, defaultValue: 0.5) +// maximumCornerRadiusSliderView.configureWith( +// title: "Max corner radius", minValue: 0, maxValue: 30, +// initialValue: 15, defaultValue: 15) hasFixedHeightSwitch.isOn = hasFixedHeight coversStatusBarSwitch.isOn = coversStatusBar supportsPartialExpansionSwitch.isOn = supportsPartialExpansion @@ -136,20 +115,6 @@ private extension PresenterViewController { dismissableByOutsideTapButton.setTitle("\(numberOfTapsForOutsideDrawerDismissal)", for: .normal) } - func handleSliderSideButtonTapped(_ button: UIButton) { - enum ButtonTag: Int { - case min = 0 - case max - } - guard let btnTag = ButtonTag(rawValue: button.tag) else { return } - switch btnTag { - case .min: - duration = durationSlider.minimumValue - case .max: - duration = durationSlider.maximumValue - } - } - func handleSwitchToggled(_ toggler: UISwitch) { switch toggler { case hasFixedHeightSwitch: @@ -180,19 +145,10 @@ private extension PresenterViewController { return } } +} - func set(duration: Float) { - let sanitisedDuration = sanitize(duration: duration) - let nsDuration = NSNumber(value: sanitisedDuration) - durationField.text = PresenterViewController.formatter.string(from: nsDuration) - durationSlider.value = sanitisedDuration - _duration = TimeInterval(sanitisedDuration) - } - - func sanitize(duration: Float) -> Float { - let minDuration = durationSlider.minimumValue - let maxDuration = durationSlider.maximumValue - let value = min(maxDuration, max(duration, minDuration)) - return Float(truncf(10 * value)) / 10 +extension Double { + var cgFloat: CGFloat { + return CGFloat(self) } } diff --git a/DrawerKitDemo/DrawerKitDemo/SliderView.swift b/DrawerKitDemo/DrawerKitDemo/SliderView.swift new file mode 100644 index 0000000..b1ba1eb --- /dev/null +++ b/DrawerKitDemo/DrawerKitDemo/SliderView.swift @@ -0,0 +1,117 @@ +import UIKit + +@IBDesignable +class SliderView: UIView { + static func loadFromNib() -> SliderView { + let nib = UINib(nibName: "SliderView", bundle: nil) + let items = nib.instantiate(withOwner: nil, options: nil) + let view = items.filter { $0 is SliderView }.first as? SliderView + return view! + } + + func configureWith(title: String, + minValue: Double, maxValue: Double, + initialValue: Double, defaultValue: Double) { + let minV = min(minValue, maxValue) + let maxV = max(minValue, maxValue) + let initialV = min(max(minValue, initialValue), maxValue) + let defaultV = min(max(minValue, defaultValue), maxValue) + titleLabel.text = title + minValueLabel.text = format(value: minV) + maxValueLabel.text = format(value: maxV) + minValueButton.tag = ButtonTag.min.rawValue + maxValueButton.tag = ButtonTag.max.rawValue + textField.delegate = self + slider.minimumValue = Float(minV) + slider.maximumValue = Float(maxV) + slider.value = Float(initialV) + self.defaultValue = defaultV + } + + var value: Double { + get { return Double(_value) } + set { + let currentValue = Double(_value) + guard newValue != currentValue else { return } + set(value: newValue) + } + } + + @IBOutlet weak var minValueLabel: UILabel! + @IBOutlet weak var maxValueLabel: UILabel! + @IBOutlet weak var minValueButton: UIButton! + @IBOutlet weak var maxValueButton: UIButton! + @IBOutlet weak var titleLabel: UILabel! + @IBOutlet weak var textField: UITextField! + @IBOutlet weak var slider: UISlider! + + private var defaultValue: Double = 0 + private var _value: Double = 0 + + let formatter: NumberFormatter = { + let f = NumberFormatter() + f.allowsFloats = true + f.minimumIntegerDigits = 1 + f.minimumFractionDigits = 1 + f.maximumFractionDigits = 1 + return f + }() +} + +private extension SliderView { + enum ButtonTag: Int { + case min = 0 + case max + } + + @IBAction func buttonTapped(sender: UIButton) { + guard let btnTag = ButtonTag(rawValue: sender.tag) else { return } + switch btnTag { + case .min: + value = Double(slider.minimumValue) + case .max: + value = Double(slider.maximumValue) + } + } + + @IBAction func sliderValueChanged() { + guard value != Double(slider.value) else { return } + value = Double(slider.value) + } +} + +extension SliderView: UITextFieldDelegate { + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() + return true + } + + func textFieldDidEndEditing(_ textField: UITextField) { + guard let value = Double(textField.text ?? "0") else { + self.value = defaultValue + return + } + self.value = value + } +} + +private extension SliderView { + func set(value: Double) { + let sanitisedValue = sanitize(value: value) + textField.text = format(value: sanitisedValue) + slider.value = Float(sanitisedValue) + _value = Double(sanitisedValue) + } + + func sanitize(value: Double) -> Double { + let minValue = Double(slider.minimumValue) + let maxValue = Double(slider.maximumValue) + let value = min(maxValue, max(value, minValue)) + return Double(trunc(10 * value)) / 10 + } + + func format(value: Double) -> String? { + let nsValue = NSNumber(value: value) + return formatter.string(from: nsValue) + } +} diff --git a/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresenterVC.storyboard b/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresenterVC.storyboard index 87b38b9..03a4d96 100644 --- a/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresenterVC.storyboard +++ b/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresenterVC.storyboard @@ -21,23 +21,33 @@ - - + + + - - + + - - - - - + + + + + @@ -46,26 +56,26 @@ + - + - - - + + - - - - - + + + + + @@ -74,24 +84,23 @@ - + - - + - - + + - + @@ -102,19 +111,18 @@ + + - - - - - + + - + @@ -122,13 +130,13 @@ - + @@ -141,25 +149,24 @@ - - - + + - - + + - + @@ -171,24 +178,23 @@ + - + - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - @@ -328,10 +274,13 @@ - - + + + + + diff --git a/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/SliderView.xib b/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/SliderView.xib new file mode 100644 index 0000000..8d0860e --- /dev/null +++ b/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/SliderView.xib @@ -0,0 +1,187 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 6e98524ddc6e3491d452dabf9c0632ad799bc78e Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Thu, 12 Oct 2017 03:11:33 +0100 Subject: [PATCH 16/85] Added debug mode. --- ...DisplayController+InternalExtensions.swift | 3 +- .../Internal API/PresentationController.swift | 36 ++++++++++++++++++- .../Public API/DrawerDisplayController.swift | 6 +++- .../PresenterViewController.swift | 3 +- 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+InternalExtensions.swift b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+InternalExtensions.swift index 18b00b1..59efa2e 100755 --- a/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+InternalExtensions.swift +++ b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+InternalExtensions.swift @@ -6,7 +6,8 @@ extension DrawerDisplayController: UIViewControllerTransitioningDelegate { source: UIViewController) -> UIPresentationController? { let presentationController = PresentationController(presentingVC: presentingVC, presentedVC: presented, - configuration: configuration) + configuration: configuration, + inDebugMode: inDebugMode) presentationController.delegate = self return presentationController } diff --git a/DrawerKit/DrawerKit/Internal API/PresentationController.swift b/DrawerKit/DrawerKit/Internal API/PresentationController.swift index 42a1122..d349baa 100644 --- a/DrawerKit/DrawerKit/Internal API/PresentationController.swift +++ b/DrawerKit/DrawerKit/Internal API/PresentationController.swift @@ -5,10 +5,12 @@ final class PresentationController: UIPresentationController { private var lastDrawerY: CGFloat = 0 private var containerViewDismissalTapGR: UITapGestureRecognizer? private var presentedViewDragGR: UIPanGestureRecognizer? + private let inDebugMode: Bool init(presentingVC: UIViewController?, presentedVC: UIViewController, - configuration: DrawerConfiguration) { + configuration: DrawerConfiguration, inDebugMode: Bool = false) { self.configuration = configuration + self.inDebugMode = inDebugMode super.init(presentedViewController: presentedVC, presenting: presentingVC) } } @@ -26,6 +28,7 @@ extension PresentationController { containerView?.backgroundColor = .clear setupContainerViewDismissalTapRecogniser() setupPresentedViewDragRecogniser() + setupHeightMarks() addCornerRadiusAnimationEnding(at: drawerPartialY) } @@ -305,3 +308,34 @@ private extension PresentationController { } } } + +private extension PresentationController { + func setupHeightMarks() { + guard inDebugMode else { return } + guard let containerView = containerView else { return } + + let upperMarkYView = UIView() + upperMarkYView.backgroundColor = .red + upperMarkYView.frame = CGRect(x: 0, + y: upperMarkY, + width: containerView.bounds.size.width, + height: 3) + containerView.addSubview(upperMarkYView) + + let lowerMarkYView = UIView() + lowerMarkYView.backgroundColor = .red + lowerMarkYView.frame = CGRect(x: 0, + y: lowerMarkY, + width: containerView.bounds.size.width, + height: 3) + containerView.addSubview(lowerMarkYView) + + let drawerMarkView = UIView() + drawerMarkView.backgroundColor = .white + drawerMarkView.frame = CGRect(x: 0, + y: drawerPartialY, + width: containerView.bounds.size.width, + height: 3) + containerView.addSubview(drawerMarkView) + } +} diff --git a/DrawerKit/DrawerKit/Public API/DrawerDisplayController.swift b/DrawerKit/DrawerKit/Public API/DrawerDisplayController.swift index d98c49e..ae98b19 100755 --- a/DrawerKit/DrawerKit/Public API/DrawerDisplayController.swift +++ b/DrawerKit/DrawerKit/Public API/DrawerDisplayController.swift @@ -6,12 +6,16 @@ public final class DrawerDisplayController: NSObject { weak var presentingVC: (UIViewController & DrawerPresenting)? /* strong */ var presentedVC: (UIViewController & DrawerPresentable) + let inDebugMode: Bool + public init(presentingViewController: (UIViewController & DrawerPresenting), presentedViewController: (UIViewController & DrawerPresentable), - configuration: DrawerConfiguration = DrawerConfiguration()) { + configuration: DrawerConfiguration = DrawerConfiguration(), + inDebugMode: Bool = false) { self.presentingVC = presentingViewController self.presentedVC = presentedViewController self.configuration = configuration + self.inDebugMode = inDebugMode super.init() presentedViewController.transitioningDelegate = self presentedViewController.modalPresentationStyle = .custom diff --git a/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift b/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift index 6e23c5a..b6076b7 100644 --- a/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift +++ b/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift @@ -68,7 +68,8 @@ extension PresenterViewController { drawerDisplayController = DrawerDisplayController(presentingViewController: self, presentedViewController: vc, - configuration: configuration) + configuration: configuration, + inDebugMode: true) // ============================================ // present(vc, animated: true) From dced8815e0232f6cc32386cad87e7deb37615454 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Thu, 12 Oct 2017 03:20:47 +0100 Subject: [PATCH 17/85] Added some TODOs --- .../DrawerDisplayController+InternalExtensions.swift | 3 ++- DrawerKit/DrawerKit/Internal API/TransitionGeometry.swift | 3 +++ DrawerKit/DrawerKit/Public API/DrawerDisplayController.swift | 5 +++++ DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift | 3 +++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+InternalExtensions.swift b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+InternalExtensions.swift index 59efa2e..0f3551d 100755 --- a/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+InternalExtensions.swift +++ b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+InternalExtensions.swift @@ -23,4 +23,5 @@ extension DrawerDisplayController: UIViewControllerTransitioningDelegate { } } -extension DrawerDisplayController: UIAdaptivePresentationControllerDelegate {} // XXX +// TODO: Implement support for adaptive presentations. +extension DrawerDisplayController: UIAdaptivePresentationControllerDelegate {} diff --git a/DrawerKit/DrawerKit/Internal API/TransitionGeometry.swift b/DrawerKit/DrawerKit/Internal API/TransitionGeometry.swift index cecfd55..5562013 100644 --- a/DrawerKit/DrawerKit/Internal API/TransitionGeometry.swift +++ b/DrawerKit/DrawerKit/Internal API/TransitionGeometry.swift @@ -1,5 +1,8 @@ import UIKit +// TODO: Implement the features that depend on this struct. +// For the moment, this is not being used anywhere. + struct TransitionGeometry { var userInterfaceOrientation: UIInterfaceOrientation var actualStatusBarHeight: CGFloat diff --git a/DrawerKit/DrawerKit/Public API/DrawerDisplayController.swift b/DrawerKit/DrawerKit/Public API/DrawerDisplayController.swift index ae98b19..7ca42d9 100755 --- a/DrawerKit/DrawerKit/Public API/DrawerDisplayController.swift +++ b/DrawerKit/DrawerKit/Public API/DrawerDisplayController.swift @@ -1,5 +1,10 @@ import UIKit +// TODO: +// - support device interface orientation changes +// - support fixed height content +// - support not-covering status bar and/or gap at top + public final class DrawerDisplayController: NSObject { public let configuration: DrawerConfiguration // intentionally immutable diff --git a/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift b/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift index b6076b7..c966f68 100644 --- a/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift +++ b/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift @@ -1,6 +1,9 @@ import UIKit import DrawerKit +// TODO: +// - add the remainder of configuration controls and hook them up + // Search for the string 'THIS IS THE IMPORTANT PART' in both view controllers // to see how to show the drawer. There may be more than one important part in // each view controller. From a422b3d5152b8c63196c9373a85df1d32ab5aa5a Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Thu, 12 Oct 2017 11:12:40 +0100 Subject: [PATCH 18/85] Renamed a function to make it clear it's a debugging feature. --- DrawerKit/DrawerKit/Internal API/PresentationController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DrawerKit/DrawerKit/Internal API/PresentationController.swift b/DrawerKit/DrawerKit/Internal API/PresentationController.swift index d349baa..565bb0e 100644 --- a/DrawerKit/DrawerKit/Internal API/PresentationController.swift +++ b/DrawerKit/DrawerKit/Internal API/PresentationController.swift @@ -28,7 +28,7 @@ extension PresentationController { containerView?.backgroundColor = .clear setupContainerViewDismissalTapRecogniser() setupPresentedViewDragRecogniser() - setupHeightMarks() + setupDebugHeightMarks() addCornerRadiusAnimationEnding(at: drawerPartialY) } @@ -310,7 +310,7 @@ private extension PresentationController { } private extension PresentationController { - func setupHeightMarks() { + func setupDebugHeightMarks() { guard inDebugMode else { return } guard let containerView = containerView else { return } From 3e736d12fbd774a337bdfcefb748f5a8ff93ace2 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Thu, 12 Oct 2017 11:23:15 +0100 Subject: [PATCH 19/85] Minor refactoring. --- DrawerKit/DrawerKit.xcodeproj/project.pbxproj | 4 ++ ...PresentationController+Configuration.swift | 47 +++++++++++++++ .../Internal API/PresentationController.swift | 58 ++----------------- 3 files changed, 56 insertions(+), 53 deletions(-) create mode 100644 DrawerKit/DrawerKit/Internal API/Extensions/PresentationController+Configuration.swift diff --git a/DrawerKit/DrawerKit.xcodeproj/project.pbxproj b/DrawerKit/DrawerKit.xcodeproj/project.pbxproj index 1bf5028..e217666 100644 --- a/DrawerKit/DrawerKit.xcodeproj/project.pbxproj +++ b/DrawerKit/DrawerKit.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ CB2CB7AB1F8E9BB400AA152D /* TransitionGeometry.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7921F8E934500AA152D /* TransitionGeometry.swift */; }; CB2CB7AD1F8E9D9900AA152D /* DrawerDisplayController+InternalExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7AC1F8E9D9900AA152D /* DrawerDisplayController+InternalExtensions.swift */; }; CB2CB7AF1F8E9F4C00AA152D /* DrawerDisplayController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7AE1F8E9F4C00AA152D /* DrawerDisplayController+Extensions.swift */; }; + CB9101C61F8F791A000EAC41 /* PresentationController+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB9101C51F8F791A000EAC41 /* PresentationController+Configuration.swift */; }; CBBA2D5C1F8E815B00E0137F /* DrawerKit.h in Headers */ = {isa = PBXBuildFile; fileRef = CBBA2D5A1F8E815B00E0137F /* DrawerKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; /* End PBXBuildFile section */ @@ -31,6 +32,7 @@ CB2CB7A61F8E98F100AA152D /* TransitionConfiguration+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TransitionConfiguration+Extensions.swift"; sourceTree = ""; }; CB2CB7AC1F8E9D9900AA152D /* DrawerDisplayController+InternalExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DrawerDisplayController+InternalExtensions.swift"; sourceTree = ""; }; CB2CB7AE1F8E9F4C00AA152D /* DrawerDisplayController+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DrawerDisplayController+Extensions.swift"; sourceTree = ""; }; + CB9101C51F8F791A000EAC41 /* PresentationController+Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PresentationController+Configuration.swift"; sourceTree = ""; }; CBBA2D571F8E815B00E0137F /* DrawerKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DrawerKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; CBBA2D5A1F8E815B00E0137F /* DrawerKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DrawerKit.h; sourceTree = ""; }; CBBA2D5B1F8E815B00E0137F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -75,6 +77,7 @@ children = ( CB2CB7AC1F8E9D9900AA152D /* DrawerDisplayController+InternalExtensions.swift */, CB2CB7A61F8E98F100AA152D /* TransitionConfiguration+Extensions.swift */, + CB9101C51F8F791A000EAC41 /* PresentationController+Configuration.swift */, ); path = Extensions; sourceTree = ""; @@ -195,6 +198,7 @@ CB2CB7971F8E934500AA152D /* DrawerConfiguration.swift in Sources */, CB2CB7AA1F8E9AC600AA152D /* TransitionConfiguration+Extensions.swift in Sources */, CB2CB79D1F8E951900AA152D /* DrawerDisplayController.swift in Sources */, + CB9101C61F8F791A000EAC41 /* PresentationController+Configuration.swift in Sources */, CB2CB79E1F8E951900AA152D /* PresentationController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/DrawerKit/DrawerKit/Internal API/Extensions/PresentationController+Configuration.swift b/DrawerKit/DrawerKit/Internal API/Extensions/PresentationController+Configuration.swift new file mode 100644 index 0000000..3c928f3 --- /dev/null +++ b/DrawerKit/DrawerKit/Internal API/Extensions/PresentationController+Configuration.swift @@ -0,0 +1,47 @@ +import UIKit + +extension PresentationController { + var durationInSeconds: TimeInterval { + return configuration.durationInSeconds + } + + var timingCurveProvider: UITimingCurveProvider { + return configuration.timingCurveProvider + } + + var supportsPartialExpansion: Bool { + return configuration.supportsPartialExpansion + } + + var dismissesInStages: Bool { + return configuration.dismissesInStages + } + + var flickSpeedThreshold: CGFloat { + return configuration.flickSpeedThreshold + } + + var upperMarkFraction: CGFloat { + return configuration.upperMarkFraction + } + + var lowerMarkFraction: CGFloat { + return configuration.lowerMarkFraction + } + + var maximumCornerRadius: CGFloat { + return configuration.maximumCornerRadius + } + + var isDrawerDraggable: Bool { + return configuration.isDrawerDraggable + } + + var isDismissableByOutsideDrawerTaps: Bool { + return configuration.isDismissableByOutsideDrawerTaps + } + + var numberOfTapsForOutsideDrawerDismissal: Int { + return configuration.numberOfTapsForOutsideDrawerDismissal + } +} diff --git a/DrawerKit/DrawerKit/Internal API/PresentationController.swift b/DrawerKit/DrawerKit/Internal API/PresentationController.swift index 565bb0e..f78e358 100644 --- a/DrawerKit/DrawerKit/Internal API/PresentationController.swift +++ b/DrawerKit/DrawerKit/Internal API/PresentationController.swift @@ -1,7 +1,7 @@ import UIKit final class PresentationController: UIPresentationController { - private let configuration: DrawerConfiguration // intentionally immutable + let configuration: DrawerConfiguration // intentionally internal and immutable private var lastDrawerY: CGFloat = 0 private var containerViewDismissalTapGR: UITapGestureRecognizer? private var presentedViewDragGR: UIPanGestureRecognizer? @@ -64,58 +64,14 @@ private extension PresentationController { return containerViewH - drawerPartialH } - var durationInSeconds: TimeInterval { - return configuration.durationInSeconds - } - - var timingCurveProvider: UITimingCurveProvider { - return configuration.timingCurveProvider - } - - var supportsPartialExpansion: Bool { - return configuration.supportsPartialExpansion - } - - var dismissesInStages: Bool { - return configuration.dismissesInStages - } - - var flickSpeedThreshold: CGFloat { - return configuration.flickSpeedThreshold - } - - var upperMarkFraction: CGFloat { - return configuration.upperMarkFraction - } - var upperMarkY: CGFloat { return upperMarkFraction * (containerViewH - drawerPartialH) } - var lowerMarkFraction: CGFloat { - return configuration.lowerMarkFraction - } - var lowerMarkY: CGFloat { return drawerPartialY + lowerMarkFraction * drawerPartialH } - var maximumCornerRadius: CGFloat { - return configuration.maximumCornerRadius - } - - var isDrawerDraggable: Bool { - return configuration.isDrawerDraggable - } - - var isDismissableByOutsideDrawerTaps: Bool { - return configuration.isDismissableByOutsideDrawerTaps - } - - var numberOfTapsForOutsideDrawerDismissal: Int { - return configuration.numberOfTapsForOutsideDrawerDismissal - } - var currentDrawerY: CGFloat { get { return presentedView?.frame.origin.y ?? 0 } set { presentedView?.frame.origin.y = newValue } @@ -214,10 +170,8 @@ private extension PresentationController { let endPosY = (clamping ? clamped(endPositionY) : endPositionY) guard endPosY != currentDrawerY else { return } - let duration = configuration.durationInSeconds - let timingParams = configuration.timingCurveProvider - let animator = UIViewPropertyAnimator(duration: duration, - timingParameters: timingParams) + let animator = UIViewPropertyAnimator(duration: durationInSeconds, + timingParameters: timingCurveProvider) animator.addAnimations { [weak self] in self?.currentDrawerY = endPosY @@ -239,10 +193,8 @@ private extension PresentationController { let endPosY = (clamping ? clamped(endPositionY) : endPositionY) guard endPosY != currentDrawerY else { return } - let duration = configuration.durationInSeconds - let timingParams = configuration.timingCurveProvider - let animator = UIViewPropertyAnimator(duration: duration, - timingParameters: timingParams) + let animator = UIViewPropertyAnimator(duration: durationInSeconds, + timingParameters: timingCurveProvider) let endingCornerRadius = cornerRadius(at: endPosY) animator.addAnimations { [weak self] in From f9ec01e7893ebb40469493d27c2ad9674c97f1e7 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Thu, 12 Oct 2017 11:27:27 +0100 Subject: [PATCH 20/85] Renamed some source files for better clarity of intention. --- DrawerKit/DrawerKit.xcodeproj/project.pbxproj | 24 +++++++++---------- ... DrawerDisplayController+Extensions.swift} | 0 ...> TransitionConfiguration+Equatable.swift} | 0 ...awerDisplayController+Configuration.swift} | 0 4 files changed, 12 insertions(+), 12 deletions(-) rename DrawerKit/DrawerKit/Internal API/Extensions/{DrawerDisplayController+InternalExtensions.swift => DrawerDisplayController+Extensions.swift} (100%) rename DrawerKit/DrawerKit/Internal API/Extensions/{TransitionConfiguration+Extensions.swift => TransitionConfiguration+Equatable.swift} (100%) rename DrawerKit/DrawerKit/Public API/{DrawerDisplayController+Extensions.swift => DrawerDisplayController+Configuration.swift} (100%) diff --git a/DrawerKit/DrawerKit.xcodeproj/project.pbxproj b/DrawerKit/DrawerKit.xcodeproj/project.pbxproj index e217666..9a2307c 100644 --- a/DrawerKit/DrawerKit.xcodeproj/project.pbxproj +++ b/DrawerKit/DrawerKit.xcodeproj/project.pbxproj @@ -13,10 +13,10 @@ CB2CB79D1F8E951900AA152D /* DrawerDisplayController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB79A1F8E951900AA152D /* DrawerDisplayController.swift */; }; CB2CB79E1F8E951900AA152D /* PresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB79B1F8E951900AA152D /* PresentationController.swift */; }; CB2CB79F1F8E951900AA152D /* TransitionAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB79C1F8E951900AA152D /* TransitionAnimator.swift */; }; - CB2CB7AA1F8E9AC600AA152D /* TransitionConfiguration+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7A61F8E98F100AA152D /* TransitionConfiguration+Extensions.swift */; }; + CB2CB7AA1F8E9AC600AA152D /* TransitionConfiguration+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7A61F8E98F100AA152D /* TransitionConfiguration+Equatable.swift */; }; CB2CB7AB1F8E9BB400AA152D /* TransitionGeometry.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7921F8E934500AA152D /* TransitionGeometry.swift */; }; - CB2CB7AD1F8E9D9900AA152D /* DrawerDisplayController+InternalExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7AC1F8E9D9900AA152D /* DrawerDisplayController+InternalExtensions.swift */; }; - CB2CB7AF1F8E9F4C00AA152D /* DrawerDisplayController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7AE1F8E9F4C00AA152D /* DrawerDisplayController+Extensions.swift */; }; + CB2CB7AD1F8E9D9900AA152D /* DrawerDisplayController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7AC1F8E9D9900AA152D /* DrawerDisplayController+Extensions.swift */; }; + CB2CB7AF1F8E9F4C00AA152D /* DrawerDisplayController+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7AE1F8E9F4C00AA152D /* DrawerDisplayController+Configuration.swift */; }; CB9101C61F8F791A000EAC41 /* PresentationController+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB9101C51F8F791A000EAC41 /* PresentationController+Configuration.swift */; }; CBBA2D5C1F8E815B00E0137F /* DrawerKit.h in Headers */ = {isa = PBXBuildFile; fileRef = CBBA2D5A1F8E815B00E0137F /* DrawerKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; /* End PBXBuildFile section */ @@ -29,9 +29,9 @@ CB2CB79A1F8E951900AA152D /* DrawerDisplayController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DrawerDisplayController.swift; sourceTree = ""; }; CB2CB79B1F8E951900AA152D /* PresentationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentationController.swift; sourceTree = ""; }; CB2CB79C1F8E951900AA152D /* TransitionAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransitionAnimator.swift; sourceTree = ""; }; - CB2CB7A61F8E98F100AA152D /* TransitionConfiguration+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TransitionConfiguration+Extensions.swift"; sourceTree = ""; }; - CB2CB7AC1F8E9D9900AA152D /* DrawerDisplayController+InternalExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DrawerDisplayController+InternalExtensions.swift"; sourceTree = ""; }; - CB2CB7AE1F8E9F4C00AA152D /* DrawerDisplayController+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DrawerDisplayController+Extensions.swift"; sourceTree = ""; }; + CB2CB7A61F8E98F100AA152D /* TransitionConfiguration+Equatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TransitionConfiguration+Equatable.swift"; sourceTree = ""; }; + CB2CB7AC1F8E9D9900AA152D /* DrawerDisplayController+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DrawerDisplayController+Extensions.swift"; sourceTree = ""; }; + CB2CB7AE1F8E9F4C00AA152D /* DrawerDisplayController+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DrawerDisplayController+Configuration.swift"; sourceTree = ""; }; CB9101C51F8F791A000EAC41 /* PresentationController+Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PresentationController+Configuration.swift"; sourceTree = ""; }; CBBA2D571F8E815B00E0137F /* DrawerKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DrawerKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; CBBA2D5A1F8E815B00E0137F /* DrawerKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DrawerKit.h; sourceTree = ""; }; @@ -55,7 +55,7 @@ CB2CB78E1F8E934500AA152D /* DrawerPresenting.swift */, CB2CB78D1F8E934500AA152D /* DrawerPresentable.swift */, CB2CB79A1F8E951900AA152D /* DrawerDisplayController.swift */, - CB2CB7AE1F8E9F4C00AA152D /* DrawerDisplayController+Extensions.swift */, + CB2CB7AE1F8E9F4C00AA152D /* DrawerDisplayController+Configuration.swift */, CB2CB7911F8E934500AA152D /* DrawerConfiguration.swift */, ); path = "Public API"; @@ -75,8 +75,8 @@ CB2CB7A41F8E98F100AA152D /* Extensions */ = { isa = PBXGroup; children = ( - CB2CB7AC1F8E9D9900AA152D /* DrawerDisplayController+InternalExtensions.swift */, - CB2CB7A61F8E98F100AA152D /* TransitionConfiguration+Extensions.swift */, + CB2CB7AC1F8E9D9900AA152D /* DrawerDisplayController+Extensions.swift */, + CB2CB7A61F8E98F100AA152D /* TransitionConfiguration+Equatable.swift */, CB9101C51F8F791A000EAC41 /* PresentationController+Configuration.swift */, ); path = Extensions; @@ -190,13 +190,13 @@ buildActionMask = 2147483647; files = ( CB2CB7941F8E934500AA152D /* DrawerPresentable.swift in Sources */, - CB2CB7AD1F8E9D9900AA152D /* DrawerDisplayController+InternalExtensions.swift in Sources */, + CB2CB7AD1F8E9D9900AA152D /* DrawerDisplayController+Extensions.swift in Sources */, CB2CB79F1F8E951900AA152D /* TransitionAnimator.swift in Sources */, CB2CB7951F8E934500AA152D /* DrawerPresenting.swift in Sources */, - CB2CB7AF1F8E9F4C00AA152D /* DrawerDisplayController+Extensions.swift in Sources */, + CB2CB7AF1F8E9F4C00AA152D /* DrawerDisplayController+Configuration.swift in Sources */, CB2CB7AB1F8E9BB400AA152D /* TransitionGeometry.swift in Sources */, CB2CB7971F8E934500AA152D /* DrawerConfiguration.swift in Sources */, - CB2CB7AA1F8E9AC600AA152D /* TransitionConfiguration+Extensions.swift in Sources */, + CB2CB7AA1F8E9AC600AA152D /* TransitionConfiguration+Equatable.swift in Sources */, CB2CB79D1F8E951900AA152D /* DrawerDisplayController.swift in Sources */, CB9101C61F8F791A000EAC41 /* PresentationController+Configuration.swift in Sources */, CB2CB79E1F8E951900AA152D /* PresentationController.swift in Sources */, diff --git a/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+InternalExtensions.swift b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift similarity index 100% rename from DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+InternalExtensions.swift rename to DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift diff --git a/DrawerKit/DrawerKit/Internal API/Extensions/TransitionConfiguration+Extensions.swift b/DrawerKit/DrawerKit/Internal API/Extensions/TransitionConfiguration+Equatable.swift similarity index 100% rename from DrawerKit/DrawerKit/Internal API/Extensions/TransitionConfiguration+Extensions.swift rename to DrawerKit/DrawerKit/Internal API/Extensions/TransitionConfiguration+Equatable.swift diff --git a/DrawerKit/DrawerKit/Public API/DrawerDisplayController+Extensions.swift b/DrawerKit/DrawerKit/Public API/DrawerDisplayController+Configuration.swift similarity index 100% rename from DrawerKit/DrawerKit/Public API/DrawerDisplayController+Extensions.swift rename to DrawerKit/DrawerKit/Public API/DrawerDisplayController+Configuration.swift From a0baa3b4f1d954fcf2ae5dcd984cecb7d0724d54 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Thu, 12 Oct 2017 11:42:03 +0100 Subject: [PATCH 21/85] Removed source headers. --- DrawerKit/DrawerKit/DrawerKit.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/DrawerKit/DrawerKit/DrawerKit.h b/DrawerKit/DrawerKit/DrawerKit.h index 1d93658..69d5548 100644 --- a/DrawerKit/DrawerKit/DrawerKit.h +++ b/DrawerKit/DrawerKit/DrawerKit.h @@ -1,11 +1,3 @@ -// -// DrawerKit.h -// DrawerKit -// -// Created by Wagner Truppel on 11/10/2017. -// Copyright © 2017 Babylon Health. All rights reserved. -// - #import //! Project version number for DrawerKit. From 39ca74d1e98372d81e73eb2a19650af585b8d46a Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Thu, 12 Oct 2017 11:51:55 +0100 Subject: [PATCH 22/85] Changed the presented VC to make a larger drawer (easier to play with). --- .../Base.lproj/PresentedVC.storyboard | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresentedVC.storyboard b/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresentedVC.storyboard index 837cf0c..561b6c1 100644 --- a/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresentedVC.storyboard +++ b/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresentedVC.storyboard @@ -1,11 +1,11 @@ - + - + @@ -36,28 +36,28 @@ - + - + @@ -66,7 +66,7 @@ - + - + - + @@ -63,16 +59,16 @@ - + - + @@ -91,16 +87,16 @@ - + - + @@ -119,10 +115,10 @@ - + - + @@ -130,13 +126,13 @@ - + @@ -157,16 +153,16 @@ - + - + @@ -185,16 +181,16 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -93,15 +116,17 @@ + + + - - + From d8d22710e861204a96727486de29227be4af0725 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Thu, 12 Oct 2017 20:41:39 +0100 Subject: [PATCH 33/85] Added an interaction controller, the first step in fixing the non-interactive presentation/dismissal itself. --- DrawerKit/DrawerKit.xcodeproj/project.pbxproj | 4 ++++ .../DrawerDisplayController+Extensions.swift | 8 ++++++++ .../Internal API/InteractionController.swift | 18 ++++++++++++++++++ 3 files changed, 30 insertions(+) create mode 100644 DrawerKit/DrawerKit/Internal API/InteractionController.swift diff --git a/DrawerKit/DrawerKit.xcodeproj/project.pbxproj b/DrawerKit/DrawerKit.xcodeproj/project.pbxproj index f56d271..a895dae 100644 --- a/DrawerKit/DrawerKit.xcodeproj/project.pbxproj +++ b/DrawerKit/DrawerKit.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ CB2CB7AB1F8E9BB400AA152D /* TransitionGeometry.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7921F8E934500AA152D /* TransitionGeometry.swift */; }; CB2CB7AD1F8E9D9900AA152D /* DrawerDisplayController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7AC1F8E9D9900AA152D /* DrawerDisplayController+Extensions.swift */; }; CB2CB7AF1F8E9F4C00AA152D /* DrawerDisplayController+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7AE1F8E9F4C00AA152D /* DrawerDisplayController+Configuration.swift */; }; + CB619CEC1F8FFBAD0076E1DE /* InteractionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB619CEB1F8FFBAD0076E1DE /* InteractionController.swift */; }; CB9101C61F8F791A000EAC41 /* PresentationController+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB9101C51F8F791A000EAC41 /* PresentationController+Configuration.swift */; }; CBBA2D5C1F8E815B00E0137F /* DrawerKit.h in Headers */ = {isa = PBXBuildFile; fileRef = CBBA2D5A1F8E815B00E0137F /* DrawerKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; /* End PBXBuildFile section */ @@ -32,6 +33,7 @@ CB2CB7A61F8E98F100AA152D /* TransitionConfiguration+Equatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TransitionConfiguration+Equatable.swift"; sourceTree = ""; }; CB2CB7AC1F8E9D9900AA152D /* DrawerDisplayController+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DrawerDisplayController+Extensions.swift"; sourceTree = ""; }; CB2CB7AE1F8E9F4C00AA152D /* DrawerDisplayController+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DrawerDisplayController+Configuration.swift"; sourceTree = ""; }; + CB619CEB1F8FFBAD0076E1DE /* InteractionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InteractionController.swift; sourceTree = ""; }; CB9101C51F8F791A000EAC41 /* PresentationController+Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PresentationController+Configuration.swift"; sourceTree = ""; }; CBBA2D571F8E815B00E0137F /* DrawerKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DrawerKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; CBBA2D5A1F8E815B00E0137F /* DrawerKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DrawerKit.h; sourceTree = ""; }; @@ -65,6 +67,7 @@ isa = PBXGroup; children = ( CB2CB79B1F8E951900AA152D /* PresentationController.swift */, + CB619CEB1F8FFBAD0076E1DE /* InteractionController.swift */, CB2CB79C1F8E951900AA152D /* TransitionAnimator.swift */, CB2CB7921F8E934500AA152D /* TransitionGeometry.swift */, CB2CB7A41F8E98F100AA152D /* Extensions */, @@ -190,6 +193,7 @@ buildActionMask = 2147483647; files = ( CB2CB7941F8E934500AA152D /* DrawerPresentable.swift in Sources */, + CB619CEC1F8FFBAD0076E1DE /* InteractionController.swift in Sources */, CB2CB7AD1F8E9D9900AA152D /* DrawerDisplayController+Extensions.swift in Sources */, CB2CB79F1F8E951900AA152D /* TransitionAnimator.swift in Sources */, CB2CB7951F8E934500AA152D /* DrawerPresenting.swift in Sources */, diff --git a/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift index 0f3551d..25c2165 100755 --- a/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift +++ b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift @@ -21,6 +21,14 @@ extension DrawerDisplayController: UIViewControllerTransitioningDelegate { public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return TransitionAnimator(isPresentation: false, configuration: configuration) } + + public func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { + return InteractionController(isPresentation: true, configuration: configuration) + } + + public func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { + return InteractionController(isPresentation: false, configuration: configuration) + } } // TODO: Implement support for adaptive presentations. diff --git a/DrawerKit/DrawerKit/Internal API/InteractionController.swift b/DrawerKit/DrawerKit/Internal API/InteractionController.swift new file mode 100644 index 0000000..693b3f8 --- /dev/null +++ b/DrawerKit/DrawerKit/Internal API/InteractionController.swift @@ -0,0 +1,18 @@ +import UIKit + +final class InteractionController: NSObject { + private let configuration: DrawerConfiguration // intentionally immutable + private let isPresentation: Bool + + init(isPresentation: Bool, configuration: DrawerConfiguration) { + self.configuration = configuration + self.isPresentation = isPresentation + super.init() + } +} + +extension InteractionController: UIViewControllerInteractiveTransitioning { + func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) { + // XXX + } +} From bb5377f27ad33ad991d8e6f15713fcc54610c1b2 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Thu, 12 Oct 2017 20:43:56 +0100 Subject: [PATCH 34/85] Renamed TransitionAnimator to AnimationController. --- DrawerKit/DrawerKit.xcodeproj/project.pbxproj | 10 +++++----- ...ansitionAnimator.swift => AnimatorController.swift} | 5 ++--- .../DrawerDisplayController+Extensions.swift | 4 ++-- 3 files changed, 9 insertions(+), 10 deletions(-) rename DrawerKit/DrawerKit/Internal API/{TransitionAnimator.swift => AnimatorController.swift} (94%) diff --git a/DrawerKit/DrawerKit.xcodeproj/project.pbxproj b/DrawerKit/DrawerKit.xcodeproj/project.pbxproj index a895dae..03ad639 100644 --- a/DrawerKit/DrawerKit.xcodeproj/project.pbxproj +++ b/DrawerKit/DrawerKit.xcodeproj/project.pbxproj @@ -12,7 +12,7 @@ CB2CB7971F8E934500AA152D /* DrawerConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7911F8E934500AA152D /* DrawerConfiguration.swift */; }; CB2CB79D1F8E951900AA152D /* DrawerDisplayController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB79A1F8E951900AA152D /* DrawerDisplayController.swift */; }; CB2CB79E1F8E951900AA152D /* PresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB79B1F8E951900AA152D /* PresentationController.swift */; }; - CB2CB79F1F8E951900AA152D /* TransitionAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB79C1F8E951900AA152D /* TransitionAnimator.swift */; }; + CB2CB79F1F8E951900AA152D /* AnimatorController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB79C1F8E951900AA152D /* AnimatorController.swift */; }; CB2CB7AA1F8E9AC600AA152D /* TransitionConfiguration+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7A61F8E98F100AA152D /* TransitionConfiguration+Equatable.swift */; }; CB2CB7AB1F8E9BB400AA152D /* TransitionGeometry.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7921F8E934500AA152D /* TransitionGeometry.swift */; }; CB2CB7AD1F8E9D9900AA152D /* DrawerDisplayController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7AC1F8E9D9900AA152D /* DrawerDisplayController+Extensions.swift */; }; @@ -29,7 +29,7 @@ CB2CB7921F8E934500AA152D /* TransitionGeometry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransitionGeometry.swift; sourceTree = ""; }; CB2CB79A1F8E951900AA152D /* DrawerDisplayController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DrawerDisplayController.swift; sourceTree = ""; }; CB2CB79B1F8E951900AA152D /* PresentationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentationController.swift; sourceTree = ""; }; - CB2CB79C1F8E951900AA152D /* TransitionAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransitionAnimator.swift; sourceTree = ""; }; + CB2CB79C1F8E951900AA152D /* AnimatorController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatorController.swift; sourceTree = ""; }; CB2CB7A61F8E98F100AA152D /* TransitionConfiguration+Equatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TransitionConfiguration+Equatable.swift"; sourceTree = ""; }; CB2CB7AC1F8E9D9900AA152D /* DrawerDisplayController+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DrawerDisplayController+Extensions.swift"; sourceTree = ""; }; CB2CB7AE1F8E9F4C00AA152D /* DrawerDisplayController+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DrawerDisplayController+Configuration.swift"; sourceTree = ""; }; @@ -68,7 +68,7 @@ children = ( CB2CB79B1F8E951900AA152D /* PresentationController.swift */, CB619CEB1F8FFBAD0076E1DE /* InteractionController.swift */, - CB2CB79C1F8E951900AA152D /* TransitionAnimator.swift */, + CB2CB79C1F8E951900AA152D /* AnimatorController.swift */, CB2CB7921F8E934500AA152D /* TransitionGeometry.swift */, CB2CB7A41F8E98F100AA152D /* Extensions */, ); @@ -79,8 +79,8 @@ isa = PBXGroup; children = ( CB2CB7AC1F8E9D9900AA152D /* DrawerDisplayController+Extensions.swift */, - CB2CB7A61F8E98F100AA152D /* TransitionConfiguration+Equatable.swift */, CB9101C51F8F791A000EAC41 /* PresentationController+Configuration.swift */, + CB2CB7A61F8E98F100AA152D /* TransitionConfiguration+Equatable.swift */, ); path = Extensions; sourceTree = ""; @@ -195,7 +195,7 @@ CB2CB7941F8E934500AA152D /* DrawerPresentable.swift in Sources */, CB619CEC1F8FFBAD0076E1DE /* InteractionController.swift in Sources */, CB2CB7AD1F8E9D9900AA152D /* DrawerDisplayController+Extensions.swift in Sources */, - CB2CB79F1F8E951900AA152D /* TransitionAnimator.swift in Sources */, + CB2CB79F1F8E951900AA152D /* AnimatorController.swift in Sources */, CB2CB7951F8E934500AA152D /* DrawerPresenting.swift in Sources */, CB2CB7AF1F8E9F4C00AA152D /* DrawerDisplayController+Configuration.swift in Sources */, CB2CB7AB1F8E9BB400AA152D /* TransitionGeometry.swift in Sources */, diff --git a/DrawerKit/DrawerKit/Internal API/TransitionAnimator.swift b/DrawerKit/DrawerKit/Internal API/AnimatorController.swift similarity index 94% rename from DrawerKit/DrawerKit/Internal API/TransitionAnimator.swift rename to DrawerKit/DrawerKit/Internal API/AnimatorController.swift index 32c28ec..a1413ec 100644 --- a/DrawerKit/DrawerKit/Internal API/TransitionAnimator.swift +++ b/DrawerKit/DrawerKit/Internal API/AnimatorController.swift @@ -1,6 +1,6 @@ import UIKit -final class TransitionAnimator: NSObject { +final class AnimatorController: NSObject { private let configuration: DrawerConfiguration // intentionally immutable private let isPresentation: Bool @@ -11,8 +11,7 @@ final class TransitionAnimator: NSObject { } } -extension TransitionAnimator: UIViewControllerAnimatedTransitioning { - +extension AnimatorController: UIViewControllerAnimatedTransitioning { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return configuration.durationInSeconds } diff --git a/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift index 25c2165..ddad361 100755 --- a/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift +++ b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift @@ -15,11 +15,11 @@ extension DrawerDisplayController: UIViewControllerTransitioningDelegate { public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { - return TransitionAnimator(isPresentation: true, configuration: configuration) + return AnimatorController(isPresentation: true, configuration: configuration) } public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { - return TransitionAnimator(isPresentation: false, configuration: configuration) + return AnimatorController(isPresentation: false, configuration: configuration) } public func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { From e90bf24b09efb5f382ccd8951ff7dca1eeb52d81 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Thu, 12 Oct 2017 20:44:42 +0100 Subject: [PATCH 35/85] Renamed a file to match the extension it's about. --- DrawerKit/DrawerKit.xcodeproj/project.pbxproj | 8 ++++---- ...quatable.swift => DrawerConfiguration+Equatable.swift} | 0 2 files changed, 4 insertions(+), 4 deletions(-) rename DrawerKit/DrawerKit/Internal API/Extensions/{TransitionConfiguration+Equatable.swift => DrawerConfiguration+Equatable.swift} (100%) diff --git a/DrawerKit/DrawerKit.xcodeproj/project.pbxproj b/DrawerKit/DrawerKit.xcodeproj/project.pbxproj index 03ad639..f0316ea 100644 --- a/DrawerKit/DrawerKit.xcodeproj/project.pbxproj +++ b/DrawerKit/DrawerKit.xcodeproj/project.pbxproj @@ -13,7 +13,7 @@ CB2CB79D1F8E951900AA152D /* DrawerDisplayController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB79A1F8E951900AA152D /* DrawerDisplayController.swift */; }; CB2CB79E1F8E951900AA152D /* PresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB79B1F8E951900AA152D /* PresentationController.swift */; }; CB2CB79F1F8E951900AA152D /* AnimatorController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB79C1F8E951900AA152D /* AnimatorController.swift */; }; - CB2CB7AA1F8E9AC600AA152D /* TransitionConfiguration+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7A61F8E98F100AA152D /* TransitionConfiguration+Equatable.swift */; }; + CB2CB7AA1F8E9AC600AA152D /* DrawerConfiguration+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7A61F8E98F100AA152D /* DrawerConfiguration+Equatable.swift */; }; CB2CB7AB1F8E9BB400AA152D /* TransitionGeometry.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7921F8E934500AA152D /* TransitionGeometry.swift */; }; CB2CB7AD1F8E9D9900AA152D /* DrawerDisplayController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7AC1F8E9D9900AA152D /* DrawerDisplayController+Extensions.swift */; }; CB2CB7AF1F8E9F4C00AA152D /* DrawerDisplayController+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7AE1F8E9F4C00AA152D /* DrawerDisplayController+Configuration.swift */; }; @@ -30,7 +30,7 @@ CB2CB79A1F8E951900AA152D /* DrawerDisplayController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DrawerDisplayController.swift; sourceTree = ""; }; CB2CB79B1F8E951900AA152D /* PresentationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentationController.swift; sourceTree = ""; }; CB2CB79C1F8E951900AA152D /* AnimatorController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatorController.swift; sourceTree = ""; }; - CB2CB7A61F8E98F100AA152D /* TransitionConfiguration+Equatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TransitionConfiguration+Equatable.swift"; sourceTree = ""; }; + CB2CB7A61F8E98F100AA152D /* DrawerConfiguration+Equatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DrawerConfiguration+Equatable.swift"; sourceTree = ""; }; CB2CB7AC1F8E9D9900AA152D /* DrawerDisplayController+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DrawerDisplayController+Extensions.swift"; sourceTree = ""; }; CB2CB7AE1F8E9F4C00AA152D /* DrawerDisplayController+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DrawerDisplayController+Configuration.swift"; sourceTree = ""; }; CB619CEB1F8FFBAD0076E1DE /* InteractionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InteractionController.swift; sourceTree = ""; }; @@ -80,7 +80,7 @@ children = ( CB2CB7AC1F8E9D9900AA152D /* DrawerDisplayController+Extensions.swift */, CB9101C51F8F791A000EAC41 /* PresentationController+Configuration.swift */, - CB2CB7A61F8E98F100AA152D /* TransitionConfiguration+Equatable.swift */, + CB2CB7A61F8E98F100AA152D /* DrawerConfiguration+Equatable.swift */, ); path = Extensions; sourceTree = ""; @@ -200,7 +200,7 @@ CB2CB7AF1F8E9F4C00AA152D /* DrawerDisplayController+Configuration.swift in Sources */, CB2CB7AB1F8E9BB400AA152D /* TransitionGeometry.swift in Sources */, CB2CB7971F8E934500AA152D /* DrawerConfiguration.swift in Sources */, - CB2CB7AA1F8E9AC600AA152D /* TransitionConfiguration+Equatable.swift in Sources */, + CB2CB7AA1F8E9AC600AA152D /* DrawerConfiguration+Equatable.swift in Sources */, CB2CB79D1F8E951900AA152D /* DrawerDisplayController.swift in Sources */, CB9101C61F8F791A000EAC41 /* PresentationController+Configuration.swift in Sources */, CB2CB79E1F8E951900AA152D /* PresentationController.swift in Sources */, diff --git a/DrawerKit/DrawerKit/Internal API/Extensions/TransitionConfiguration+Equatable.swift b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerConfiguration+Equatable.swift similarity index 100% rename from DrawerKit/DrawerKit/Internal API/Extensions/TransitionConfiguration+Equatable.swift rename to DrawerKit/DrawerKit/Internal API/Extensions/DrawerConfiguration+Equatable.swift From 4ecf66ca014c5b355c367bc69a2e3589960de81c Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Thu, 12 Oct 2017 20:46:27 +0100 Subject: [PATCH 36/85] Duh, it was supposed to be AnimationController, not AnimatorController. --- DrawerKit/DrawerKit.xcodeproj/project.pbxproj | 8 ++++---- ...AnimatorController.swift => AnimationController.swift} | 4 ++-- .../Extensions/DrawerDisplayController+Extensions.swift | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) rename DrawerKit/DrawerKit/Internal API/{AnimatorController.swift => AnimationController.swift} (94%) diff --git a/DrawerKit/DrawerKit.xcodeproj/project.pbxproj b/DrawerKit/DrawerKit.xcodeproj/project.pbxproj index f0316ea..31d3858 100644 --- a/DrawerKit/DrawerKit.xcodeproj/project.pbxproj +++ b/DrawerKit/DrawerKit.xcodeproj/project.pbxproj @@ -12,7 +12,7 @@ CB2CB7971F8E934500AA152D /* DrawerConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7911F8E934500AA152D /* DrawerConfiguration.swift */; }; CB2CB79D1F8E951900AA152D /* DrawerDisplayController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB79A1F8E951900AA152D /* DrawerDisplayController.swift */; }; CB2CB79E1F8E951900AA152D /* PresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB79B1F8E951900AA152D /* PresentationController.swift */; }; - CB2CB79F1F8E951900AA152D /* AnimatorController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB79C1F8E951900AA152D /* AnimatorController.swift */; }; + CB2CB79F1F8E951900AA152D /* AnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB79C1F8E951900AA152D /* AnimationController.swift */; }; CB2CB7AA1F8E9AC600AA152D /* DrawerConfiguration+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7A61F8E98F100AA152D /* DrawerConfiguration+Equatable.swift */; }; CB2CB7AB1F8E9BB400AA152D /* TransitionGeometry.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7921F8E934500AA152D /* TransitionGeometry.swift */; }; CB2CB7AD1F8E9D9900AA152D /* DrawerDisplayController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7AC1F8E9D9900AA152D /* DrawerDisplayController+Extensions.swift */; }; @@ -29,7 +29,7 @@ CB2CB7921F8E934500AA152D /* TransitionGeometry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransitionGeometry.swift; sourceTree = ""; }; CB2CB79A1F8E951900AA152D /* DrawerDisplayController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DrawerDisplayController.swift; sourceTree = ""; }; CB2CB79B1F8E951900AA152D /* PresentationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentationController.swift; sourceTree = ""; }; - CB2CB79C1F8E951900AA152D /* AnimatorController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatorController.swift; sourceTree = ""; }; + CB2CB79C1F8E951900AA152D /* AnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationController.swift; sourceTree = ""; }; CB2CB7A61F8E98F100AA152D /* DrawerConfiguration+Equatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DrawerConfiguration+Equatable.swift"; sourceTree = ""; }; CB2CB7AC1F8E9D9900AA152D /* DrawerDisplayController+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DrawerDisplayController+Extensions.swift"; sourceTree = ""; }; CB2CB7AE1F8E9F4C00AA152D /* DrawerDisplayController+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DrawerDisplayController+Configuration.swift"; sourceTree = ""; }; @@ -68,7 +68,7 @@ children = ( CB2CB79B1F8E951900AA152D /* PresentationController.swift */, CB619CEB1F8FFBAD0076E1DE /* InteractionController.swift */, - CB2CB79C1F8E951900AA152D /* AnimatorController.swift */, + CB2CB79C1F8E951900AA152D /* AnimationController.swift */, CB2CB7921F8E934500AA152D /* TransitionGeometry.swift */, CB2CB7A41F8E98F100AA152D /* Extensions */, ); @@ -195,7 +195,7 @@ CB2CB7941F8E934500AA152D /* DrawerPresentable.swift in Sources */, CB619CEC1F8FFBAD0076E1DE /* InteractionController.swift in Sources */, CB2CB7AD1F8E9D9900AA152D /* DrawerDisplayController+Extensions.swift in Sources */, - CB2CB79F1F8E951900AA152D /* AnimatorController.swift in Sources */, + CB2CB79F1F8E951900AA152D /* AnimationController.swift in Sources */, CB2CB7951F8E934500AA152D /* DrawerPresenting.swift in Sources */, CB2CB7AF1F8E9F4C00AA152D /* DrawerDisplayController+Configuration.swift in Sources */, CB2CB7AB1F8E9BB400AA152D /* TransitionGeometry.swift in Sources */, diff --git a/DrawerKit/DrawerKit/Internal API/AnimatorController.swift b/DrawerKit/DrawerKit/Internal API/AnimationController.swift similarity index 94% rename from DrawerKit/DrawerKit/Internal API/AnimatorController.swift rename to DrawerKit/DrawerKit/Internal API/AnimationController.swift index a1413ec..eafff9e 100644 --- a/DrawerKit/DrawerKit/Internal API/AnimatorController.swift +++ b/DrawerKit/DrawerKit/Internal API/AnimationController.swift @@ -1,6 +1,6 @@ import UIKit -final class AnimatorController: NSObject { +final class AnimationController: NSObject { private let configuration: DrawerConfiguration // intentionally immutable private let isPresentation: Bool @@ -11,7 +11,7 @@ final class AnimatorController: NSObject { } } -extension AnimatorController: UIViewControllerAnimatedTransitioning { +extension AnimationController: UIViewControllerAnimatedTransitioning { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return configuration.durationInSeconds } diff --git a/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift index ddad361..8c94186 100755 --- a/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift +++ b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift @@ -15,11 +15,11 @@ extension DrawerDisplayController: UIViewControllerTransitioningDelegate { public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { - return AnimatorController(isPresentation: true, configuration: configuration) + return AnimationController(isPresentation: true, configuration: configuration) } public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { - return AnimatorController(isPresentation: false, configuration: configuration) + return AnimationController(isPresentation: false, configuration: configuration) } public func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { From ac7c5ff31b21da107d0479e3e66a15be572829af Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Thu, 12 Oct 2017 21:56:38 +0100 Subject: [PATCH 37/85] Implemented interactive controller for the presentation and dismissal, and cleaned up the code for the animation controller (it doesn't need to know anything about the drawers). --- .../Internal API/AnimationController.swift | 17 ++--- .../DrawerDisplayController+Extensions.swift | 16 +++-- .../Internal API/InteractionController.swift | 64 +++++++++++++++++-- 3 files changed, 78 insertions(+), 19 deletions(-) diff --git a/DrawerKit/DrawerKit/Internal API/AnimationController.swift b/DrawerKit/DrawerKit/Internal API/AnimationController.swift index eafff9e..456a232 100644 --- a/DrawerKit/DrawerKit/Internal API/AnimationController.swift +++ b/DrawerKit/DrawerKit/Internal API/AnimationController.swift @@ -1,19 +1,22 @@ import UIKit final class AnimationController: NSObject { - private let configuration: DrawerConfiguration // intentionally immutable private let isPresentation: Bool + private let durationInSeconds: TimeInterval + private let timingCurveProvider: UITimingCurveProvider - init(isPresentation: Bool, configuration: DrawerConfiguration) { - self.configuration = configuration + init(isPresentation: Bool, durationInSeconds: TimeInterval, + timingCurveProvider: UITimingCurveProvider) { self.isPresentation = isPresentation + self.durationInSeconds = durationInSeconds + self.timingCurveProvider = timingCurveProvider super.init() } } extension AnimationController: UIViewControllerAnimatedTransitioning { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { - return configuration.durationInSeconds + return durationInSeconds } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { @@ -31,10 +34,8 @@ extension AnimationController: UIViewControllerAnimatedTransitioning { let initialFrame = (isPresentation ? dismissedFrame : presentedFrame) let finalFrame = (isPresentation ? presentedFrame : dismissedFrame) - let duration = configuration.durationInSeconds - let timingParams = configuration.timingCurveProvider - let animator = UIViewPropertyAnimator(duration: duration, - timingParameters: timingParams) + let animator = UIViewPropertyAnimator(duration: durationInSeconds, + timingParameters: timingCurveProvider) controller.view.frame = initialFrame animator.addAnimations { controller.view.frame = finalFrame } diff --git a/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift index 8c94186..aecd543 100755 --- a/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift +++ b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift @@ -15,19 +15,27 @@ extension DrawerDisplayController: UIViewControllerTransitioningDelegate { public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { - return AnimationController(isPresentation: true, configuration: configuration) + return AnimationController(isPresentation: true, + durationInSeconds: configuration.durationInSeconds, + timingCurveProvider: configuration.timingCurveProvider) } public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { - return AnimationController(isPresentation: false, configuration: configuration) + return AnimationController(isPresentation: false, + durationInSeconds: configuration.durationInSeconds, + timingCurveProvider: configuration.timingCurveProvider) } public func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { - return InteractionController(isPresentation: true, configuration: configuration) + guard isDrawerDraggable else { return nil } + return InteractionController(isPresentation: true, + presentingVC: presentingVC!, presentedVC: presentedVC) } public func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { - return InteractionController(isPresentation: false, configuration: configuration) + guard isDrawerDraggable else { return nil } + return InteractionController(isPresentation: true, + presentingVC: presentingVC!, presentedVC: presentedVC) } } diff --git a/DrawerKit/DrawerKit/Internal API/InteractionController.swift b/DrawerKit/DrawerKit/Internal API/InteractionController.swift index 693b3f8..e025301 100644 --- a/DrawerKit/DrawerKit/Internal API/InteractionController.swift +++ b/DrawerKit/DrawerKit/Internal API/InteractionController.swift @@ -1,18 +1,68 @@ import UIKit -final class InteractionController: NSObject { - private let configuration: DrawerConfiguration // intentionally immutable +final class InteractionController: UIPercentDrivenInteractiveTransition { private let isPresentation: Bool + private weak var presentingVC: UIViewController! + private weak var presentedVC: UIViewController! + private var presentedViewDragGR: UIPanGestureRecognizer? + private var containerH: CGFloat = 0 - init(isPresentation: Bool, configuration: DrawerConfiguration) { - self.configuration = configuration + init(isPresentation: Bool, presentingVC: UIViewController, presentedVC: UIViewController) { self.isPresentation = isPresentation + self.presentingVC = presentingVC + self.presentedVC = presentedVC super.init() + setupPresentedViewDragRecogniser() + wantsInteractiveStart = false } } -extension InteractionController: UIViewControllerInteractiveTransitioning { - func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) { - // XXX +extension InteractionController { + override func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) { + containerH = transitionContext.containerView.bounds.size.height + super.startInteractiveTransition(transitionContext) + } +} + +private extension InteractionController { + func setupPresentedViewDragRecogniser() { + guard presentedViewDragGR == nil else { return } + let gr = UIPanGestureRecognizer(target: self, + action: #selector(handlePresentedViewDrag)) + presentedVC.view.addGestureRecognizer(gr) + presentedViewDragGR = gr + } + + func removePresentedViewDragRecogniser() { + guard let gr = presentedViewDragGR else { return } + presentedVC.view.removeGestureRecognizer(gr) + presentedViewDragGR = nil + } + + @objc func handlePresentedViewDrag() { + guard let gr = presentedViewDragGR, let view = gr.view else { return } + + switch gr.state { + case .began: + if isPresentation { + presentingVC.present(presentedVC, animated: true) + } else { + presentedVC.dismiss(animated: true) + } + + case .changed: + let offsetY = gr.translation(in: view).y + gr.setTranslation(.zero, in: view) + update(percentComplete + offsetY / containerH) + + case .ended: + finish() + + case .cancelled: + cancel() + + default: + break + } } } From 6787d0792b36b35a70f878a896d15789ae9c4033 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Fri, 13 Oct 2017 11:25:31 +0100 Subject: [PATCH 38/85] Added license file and CocoaPods podspec. Not passing lint just yet. --- DrawerKit.podspec | 22 ++++++++++++++++++++++ LICENSE.md | 21 +++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 DrawerKit.podspec create mode 100644 LICENSE.md diff --git a/DrawerKit.podspec b/DrawerKit.podspec new file mode 100644 index 0000000..c134b9f --- /dev/null +++ b/DrawerKit.podspec @@ -0,0 +1,22 @@ +Pod::Spec.new do |s| + + s.name = "DrawerKit" + s.version = "0.0.1" + s.summary = "An implementation of an interactive and animated view, similar to what you see in Apple Maps" + + s.description = <<-DESC +DrawerKit allows you to modally present a view controller from another, in such a way that the +presented view controller slides up as a "drawer", much like what happens when you tap on a location +in the map when using the Apple Maps app. The library is highly configurable, with a few more options +coming soon. + DESC + + s.homepage = "https://github.com/Babylonpartners/DrawerKit/blob/master/README.md" + s.license = { :type => "MIT", :file => "LICENSE" } + s.author = { "Wagner Truppel" => "wagner.truppel@babylonhealth.com" } + s.platform = :ios, "10.3" + s.source = { :git => "github.com/Babylonpartners/DrawerKit.git", :tag => "#{s.version}" } + s.source_files = "Classes/**/*.{swift}" + s.exclude_files = "Classes/Exclude" + +end diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..f63b6e1 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Babylon Partners + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From eebde02c8a1f53113129dc48d1ab5481a727875f Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Fri, 13 Oct 2017 11:38:56 +0100 Subject: [PATCH 39/85] Removed indirect access to configuration parameters. --- .../Extensions/DrawerDisplayController+Extensions.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift index aecd543..f5513d5 100755 --- a/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift +++ b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift @@ -16,14 +16,14 @@ extension DrawerDisplayController: UIViewControllerTransitioningDelegate { presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return AnimationController(isPresentation: true, - durationInSeconds: configuration.durationInSeconds, - timingCurveProvider: configuration.timingCurveProvider) + durationInSeconds: durationInSeconds, + timingCurveProvider: timingCurveProvider) } public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return AnimationController(isPresentation: false, - durationInSeconds: configuration.durationInSeconds, - timingCurveProvider: configuration.timingCurveProvider) + durationInSeconds: durationInSeconds, + timingCurveProvider: timingCurveProvider) } public func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { From d15f081ea4c32c6c9f76ee78130565399cf1f968 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Fri, 13 Oct 2017 11:39:14 +0100 Subject: [PATCH 40/85] Removed currently unused source file TransitionGeometry. --- DrawerKit/DrawerKit.xcodeproj/project.pbxproj | 4 --- .../Internal API/TransitionGeometry.swift | 30 ------------------- 2 files changed, 34 deletions(-) delete mode 100644 DrawerKit/DrawerKit/Internal API/TransitionGeometry.swift diff --git a/DrawerKit/DrawerKit.xcodeproj/project.pbxproj b/DrawerKit/DrawerKit.xcodeproj/project.pbxproj index 31d3858..e9a9599 100644 --- a/DrawerKit/DrawerKit.xcodeproj/project.pbxproj +++ b/DrawerKit/DrawerKit.xcodeproj/project.pbxproj @@ -14,7 +14,6 @@ CB2CB79E1F8E951900AA152D /* PresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB79B1F8E951900AA152D /* PresentationController.swift */; }; CB2CB79F1F8E951900AA152D /* AnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB79C1F8E951900AA152D /* AnimationController.swift */; }; CB2CB7AA1F8E9AC600AA152D /* DrawerConfiguration+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7A61F8E98F100AA152D /* DrawerConfiguration+Equatable.swift */; }; - CB2CB7AB1F8E9BB400AA152D /* TransitionGeometry.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7921F8E934500AA152D /* TransitionGeometry.swift */; }; CB2CB7AD1F8E9D9900AA152D /* DrawerDisplayController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7AC1F8E9D9900AA152D /* DrawerDisplayController+Extensions.swift */; }; CB2CB7AF1F8E9F4C00AA152D /* DrawerDisplayController+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7AE1F8E9F4C00AA152D /* DrawerDisplayController+Configuration.swift */; }; CB619CEC1F8FFBAD0076E1DE /* InteractionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB619CEB1F8FFBAD0076E1DE /* InteractionController.swift */; }; @@ -26,7 +25,6 @@ CB2CB78D1F8E934500AA152D /* DrawerPresentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DrawerPresentable.swift; sourceTree = ""; }; CB2CB78E1F8E934500AA152D /* DrawerPresenting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DrawerPresenting.swift; sourceTree = ""; }; CB2CB7911F8E934500AA152D /* DrawerConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DrawerConfiguration.swift; sourceTree = ""; }; - CB2CB7921F8E934500AA152D /* TransitionGeometry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransitionGeometry.swift; sourceTree = ""; }; CB2CB79A1F8E951900AA152D /* DrawerDisplayController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DrawerDisplayController.swift; sourceTree = ""; }; CB2CB79B1F8E951900AA152D /* PresentationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentationController.swift; sourceTree = ""; }; CB2CB79C1F8E951900AA152D /* AnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationController.swift; sourceTree = ""; }; @@ -69,7 +67,6 @@ CB2CB79B1F8E951900AA152D /* PresentationController.swift */, CB619CEB1F8FFBAD0076E1DE /* InteractionController.swift */, CB2CB79C1F8E951900AA152D /* AnimationController.swift */, - CB2CB7921F8E934500AA152D /* TransitionGeometry.swift */, CB2CB7A41F8E98F100AA152D /* Extensions */, ); path = "Internal API"; @@ -198,7 +195,6 @@ CB2CB79F1F8E951900AA152D /* AnimationController.swift in Sources */, CB2CB7951F8E934500AA152D /* DrawerPresenting.swift in Sources */, CB2CB7AF1F8E9F4C00AA152D /* DrawerDisplayController+Configuration.swift in Sources */, - CB2CB7AB1F8E9BB400AA152D /* TransitionGeometry.swift in Sources */, CB2CB7971F8E934500AA152D /* DrawerConfiguration.swift in Sources */, CB2CB7AA1F8E9AC600AA152D /* DrawerConfiguration+Equatable.swift in Sources */, CB2CB79D1F8E951900AA152D /* DrawerDisplayController.swift in Sources */, diff --git a/DrawerKit/DrawerKit/Internal API/TransitionGeometry.swift b/DrawerKit/DrawerKit/Internal API/TransitionGeometry.swift deleted file mode 100644 index 5562013..0000000 --- a/DrawerKit/DrawerKit/Internal API/TransitionGeometry.swift +++ /dev/null @@ -1,30 +0,0 @@ -import UIKit - -// TODO: Implement the features that depend on this struct. -// For the moment, this is not being used anywhere. - -struct TransitionGeometry { - var userInterfaceOrientation: UIInterfaceOrientation - var actualStatusBarHeight: CGFloat - var navigationBarHeight: CGFloat - var heightOfPartiallyExpandedDrawer: CGFloat - - init(userInterfaceOrientation: UIInterfaceOrientation, - actualStatusBarHeight: CGFloat, - navigationBarHeight: CGFloat, - heightOfPartiallyExpandedDrawer: CGFloat) { - self.userInterfaceOrientation = userInterfaceOrientation - self.actualStatusBarHeight = actualStatusBarHeight - self.navigationBarHeight = navigationBarHeight - self.heightOfPartiallyExpandedDrawer = heightOfPartiallyExpandedDrawer - } -} - -extension TransitionGeometry: Equatable { - static func ==(lhs: TransitionGeometry, rhs: TransitionGeometry) -> Bool { - return lhs.userInterfaceOrientation == rhs.userInterfaceOrientation - && lhs.actualStatusBarHeight == rhs.actualStatusBarHeight - && lhs.navigationBarHeight == rhs.navigationBarHeight - && lhs.heightOfPartiallyExpandedDrawer == rhs.heightOfPartiallyExpandedDrawer - } -} From 37c1aada0766cb95230c777600df80a3ed39c171 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Fri, 13 Oct 2017 12:04:44 +0100 Subject: [PATCH 41/85] Fixed company name in the license file. --- LICENSE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.md b/LICENSE.md index f63b6e1..eedc27c 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017 Babylon Partners +Copyright (c) 2017 Babylon Partners Limited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 88cf9dc0656cc7d79b202969abb8db5ca0237d64 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Fri, 13 Oct 2017 14:47:34 +0100 Subject: [PATCH 42/85] Cleared the team entry in the demo app. --- .../DrawerKitDemo.xcodeproj/project.pbxproj | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj b/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj index 75fb6d2..41adb77 100644 --- a/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj +++ b/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj @@ -144,7 +144,7 @@ TargetAttributes = { CBBA2D351F8E807300E0137F = { CreatedOnToolsVersion = 9.0; - ProvisioningStyle = Automatic; + ProvisioningStyle = Manual; }; }; }; @@ -352,12 +352,13 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = TK5G2RYR3R; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = DrawerKitDemo/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.babylonhealth.DrawerKitDemo; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -367,12 +368,13 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = TK5G2RYR3R; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = DrawerKitDemo/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.babylonhealth.DrawerKitDemo; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; }; From 907befc6fa39e85bfe6f2019e459af4e2d8cd05a Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Sat, 14 Oct 2017 20:32:17 +0100 Subject: [PATCH 43/85] Demo app: adding controls for all the configurable parameters. WIP. --- .../Internal API/PresentationController.swift | 4 +- .../DrawerKitDemo.xcodeproj/project.pbxproj | 14 +- .../PresenterViewController.swift | 14 +- DrawerKitDemo/DrawerKitDemo/SliderView.swift | 327 ++++-- .../Base.lproj/PresenterVC.storyboard | 928 +++++++++++++----- .../Storyboards/Base.lproj/SliderView.xib | 187 ---- DrawerKitDemo/DrawerKitDemo/SwitchView.swift | 168 ++++ 7 files changed, 1117 insertions(+), 525 deletions(-) delete mode 100644 DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/SliderView.xib create mode 100644 DrawerKitDemo/DrawerKitDemo/SwitchView.swift diff --git a/DrawerKit/DrawerKit/Internal API/PresentationController.swift b/DrawerKit/DrawerKit/Internal API/PresentationController.swift index 079577d..5ac26b8 100644 --- a/DrawerKit/DrawerKit/Internal API/PresentationController.swift +++ b/DrawerKit/DrawerKit/Internal API/PresentationController.swift @@ -285,13 +285,13 @@ private extension PresentationController { guard let containerView = containerView else { return } let upperMarkYView = UIView() - upperMarkYView.backgroundColor = .red + upperMarkYView.backgroundColor = .black upperMarkYView.frame = CGRect(x: 0, y: upperMarkY, width: containerView.bounds.size.width, height: 3) containerView.addSubview(upperMarkYView) let lowerMarkYView = UIView() - lowerMarkYView.backgroundColor = .red + lowerMarkYView.backgroundColor = .black lowerMarkYView.frame = CGRect(x: 0, y: lowerMarkY, width: containerView.bounds.size.width, height: 3) containerView.addSubview(lowerMarkYView) diff --git a/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj b/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj index 41adb77..8068163 100644 --- a/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj +++ b/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ CB2CB7B71F8EEDF400AA152D /* SliderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7B61F8EEDF400AA152D /* SliderView.swift */; }; + CB7E08D11F8F9B9D004ACB07 /* SwitchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB7E08D01F8F9B9D004ACB07 /* SwitchView.swift */; }; CBBA2D3A1F8E807400E0137F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBA2D391F8E807400E0137F /* AppDelegate.swift */; }; CBBA2D411F8E807400E0137F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CBBA2D3F1F8E807400E0137F /* Main.storyboard */; }; CBBA2D431F8E807400E0137F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CBBA2D421F8E807400E0137F /* Assets.xcassets */; }; @@ -37,8 +38,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - CB2CB7B51F8EEB2500AA152D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/SliderView.xib; sourceTree = ""; }; CB2CB7B61F8EEDF400AA152D /* SliderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SliderView.swift; sourceTree = ""; }; + CB7E08D01F8F9B9D004ACB07 /* SwitchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchView.swift; sourceTree = ""; }; CBBA2D361F8E807400E0137F /* DrawerKitDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DrawerKitDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; CBBA2D391F8E807400E0137F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; CBBA2D401F8E807400E0137F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; @@ -92,6 +93,7 @@ CBBA2D6E1F8E83E300E0137F /* PresentedViewController.swift */, CBBA2D6D1F8E83E300E0137F /* PresentedView.swift */, CB2CB7B61F8EEDF400AA152D /* SliderView.swift */, + CB7E08D01F8F9B9D004ACB07 /* SwitchView.swift */, CBBA2D421F8E807400E0137F /* Assets.xcassets */, CBBA2D6A1F8E839200E0137F /* Storyboards */, CBBA2D471F8E807400E0137F /* Info.plist */, @@ -105,7 +107,6 @@ CBBA2D3F1F8E807400E0137F /* Main.storyboard */, CBBA2D661F8E836100E0137F /* PresenterVC.storyboard */, CBBA2D681F8E836100E0137F /* PresentedVC.storyboard */, - CB2CB7B41F8EEB2500AA152D /* SliderView.xib */, CBBA2D441F8E807400E0137F /* LaunchScreen.storyboard */, ); path = Storyboards; @@ -189,6 +190,7 @@ CBBA2D3A1F8E807400E0137F /* AppDelegate.swift in Sources */, CBBA2D721F8E83E300E0137F /* PresentedViewController.swift in Sources */, CBBA2D711F8E83E300E0137F /* PresentedView.swift in Sources */, + CB7E08D11F8F9B9D004ACB07 /* SwitchView.swift in Sources */, CB2CB7B71F8EEDF400AA152D /* SliderView.swift in Sources */, CBBA2D6F1F8E83E300E0137F /* PresenterViewController.swift in Sources */, CBBA2D701F8E83E300E0137F /* PresenterView.swift in Sources */, @@ -198,14 +200,6 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ - CB2CB7B41F8EEB2500AA152D /* SliderView.xib */ = { - isa = PBXVariantGroup; - children = ( - CB2CB7B51F8EEB2500AA152D /* Base */, - ); - name = SliderView.xib; - sourceTree = ""; - }; CBBA2D3F1F8E807400E0137F /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( diff --git a/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift b/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift index 883add6..dd29c8f 100644 --- a/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift +++ b/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift @@ -112,13 +112,13 @@ private extension PresenterViewController { // maximumCornerRadiusSliderView.configureWith( // title: "Max corner radius", minValue: 0, maxValue: 30, // initialValue: 15, defaultValue: 15) - hasFixedHeightSwitch.isOn = hasFixedHeight - coversStatusBarSwitch.isOn = coversStatusBar - supportsPartialExpansionSwitch.isOn = supportsPartialExpansion - dismissesInStagesSwitch.isEnabled = supportsPartialExpansion - dismissesInStagesSwitch.isOn = dismissesInStages - drawerDraggableSwitch.isOn = isDrawerDraggable - dismissableByOutsideTapButton.setTitle("\(numberOfTapsForOutsideDrawerDismissal)", for: .normal) +// hasFixedHeightSwitch.isOn = hasFixedHeight +// coversStatusBarSwitch.isOn = coversStatusBar +// supportsPartialExpansionSwitch.isOn = supportsPartialExpansion +// dismissesInStagesSwitch.isEnabled = supportsPartialExpansion +// dismissesInStagesSwitch.isOn = dismissesInStages +// drawerDraggableSwitch.isOn = isDrawerDraggable +// dismissableByOutsideTapButton.setTitle("\(numberOfTapsForOutsideDrawerDismissal)", for: .normal) } func handleSwitchToggled(_ toggler: UISwitch) { diff --git a/DrawerKitDemo/DrawerKitDemo/SliderView.swift b/DrawerKitDemo/DrawerKitDemo/SliderView.swift index b1ba1eb..f106c00 100644 --- a/DrawerKitDemo/DrawerKitDemo/SliderView.swift +++ b/DrawerKitDemo/DrawerKitDemo/SliderView.swift @@ -1,34 +1,45 @@ import UIKit @IBDesignable -class SliderView: UIView { - static func loadFromNib() -> SliderView { - let nib = UINib(nibName: "SliderView", bundle: nil) - let items = nib.instantiate(withOwner: nil, options: nil) - let view = items.filter { $0 is SliderView }.first as? SliderView - return view! - } - - func configureWith(title: String, - minValue: Double, maxValue: Double, - initialValue: Double, defaultValue: Double) { - let minV = min(minValue, maxValue) - let maxV = max(minValue, maxValue) - let initialV = min(max(minValue, initialValue), maxValue) - let defaultV = min(max(minValue, defaultValue), maxValue) - titleLabel.text = title - minValueLabel.text = format(value: minV) - maxValueLabel.text = format(value: maxV) - minValueButton.tag = ButtonTag.min.rawValue - maxValueButton.tag = ButtonTag.max.rawValue - textField.delegate = self - slider.minimumValue = Float(minV) - slider.maximumValue = Float(maxV) - slider.value = Float(initialV) - self.defaultValue = defaultV +class SliderView: UIControl { + let label = UILabel() + let textField = UITextField() + let minimumValueButton: UIButton = UIButton(type: .custom) + let maximumValueButton: UIButton = UIButton(type: .custom) + let slider = UISlider() + + @IBInspectable var title: String? { + get { return label.text } + set { label.text = newValue } + } + + @IBInspectable var textColor: UIColor { + get { return label.textColor } + set { + label.textColor = newValue + textField.textColor = newValue + minimumValueButton.setTitleColor(newValue, for: .normal) + maximumValueButton.setTitleColor(newValue, for: .normal) + } + } + + @IBInspectable var minimumValue: Double { + get { return Double(slider.minimumValue) } + set { + slider.minimumValue = Float(newValue) + minimumValueButton.setTitle(format(value: newValue), for: .normal) + } } - var value: Double { + @IBInspectable var maximumValue: Double { + get { return Double(slider.maximumValue) } + set { + slider.maximumValue = Float(newValue) + maximumValueButton.setTitle(format(value: newValue), for: .normal) + } + } + + @IBInspectable var value: Double { get { return Double(_value) } set { let currentValue = Double(_value) @@ -37,46 +48,215 @@ class SliderView: UIView { } } - @IBOutlet weak var minValueLabel: UILabel! - @IBOutlet weak var maxValueLabel: UILabel! - @IBOutlet weak var minValueButton: UIButton! - @IBOutlet weak var maxValueButton: UIButton! - @IBOutlet weak var titleLabel: UILabel! - @IBOutlet weak var textField: UITextField! - @IBOutlet weak var slider: UISlider! - - private var defaultValue: Double = 0 - private var _value: Double = 0 - - let formatter: NumberFormatter = { - let f = NumberFormatter() - f.allowsFloats = true - f.minimumIntegerDigits = 1 - f.minimumFractionDigits = 1 - f.maximumFractionDigits = 1 - return f - }() + @IBInspectable var maximumFractionDigits: Int { + get { return formatter.maximumFractionDigits } + set { + formatter.maximumFractionDigits = max(0, newValue) + minimumValueButton.setTitle(format(value: sanitize(value: minimumValue)), for: .normal) + maximumValueButton.setTitle(format(value: sanitize(value: maximumValue)), for: .normal) + textField.text = format(value: sanitize(value: value)) + } + } + + @IBInspectable var minimumTrackTintColor: UIColor? { + get { return slider.minimumTrackTintColor } + set { slider.minimumTrackTintColor = newValue } + } + + @IBInspectable var maximumTrackTintColor: UIColor? { + get { return slider.maximumTrackTintColor } + set { slider.maximumTrackTintColor = newValue } + } + + @IBInspectable var thumbTintColor: UIColor? { + get { return slider.thumbTintColor } + set { slider.thumbTintColor = newValue } + } + + init(title: String? = nil, + minimumValue: Double = 0, + maximumValue: Double = 1, + initialValue: Double = 0.5) { + super.init(frame: .zero) + let minValue = min(minimumValue, maximumValue) + let maxValue = max(minimumValue, maximumValue) + self.minimumValue = minValue + self.maximumValue = maxValue + self.initialValue = min(max(minValue, initialValue), maxValue) + self.title = title + setup() + } + + override init(frame: CGRect) { + super.init(frame: frame) + setup() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setup() + } + + override var intrinsicContentSize: CGSize { + return containerViewIntrinsicContentSize + } + + override var isEnabled: Bool { + didSet { + textField.isEnabled = isEnabled + minimumValueButton.isEnabled = isEnabled + maximumValueButton.isEnabled = isEnabled + slider.isEnabled = isEnabled + } + } + + override func addTarget(_ target: Any?, + action: Selector, + for controlEvents: UIControlEvents) { + slider.addTarget(target, action: action, for: controlEvents) + } + + override func removeTarget(_ target: Any?, + action: Selector?, + for controlEvents: UIControlEvents) { + slider.removeTarget(target, action: action, for: controlEvents) + } + + private var initialValue: Double = 0.5 + private var _value: Double = 0.5 + + private let containerView = UIStackView() + private let textContainer = UIStackView() + private let upperView = UIStackView() + + private let textFont = UIFont.systemFont(ofSize: 15) + private let minMaxButtonW: CGFloat = 35 + private let minMaxButtonH: CGFloat = 35 + private let formatter = NumberFormatter() } private extension SliderView { enum ButtonTag: Int { - case min = 0 - case max + case min = 0 + case max } - @IBAction func buttonTapped(sender: UIButton) { - guard let btnTag = ButtonTag(rawValue: sender.tag) else { return } - switch btnTag { - case .min: - value = Double(slider.minimumValue) - case .max: - value = Double(slider.maximumValue) - } + func setup() { + setupFormatter() + setupLabel() + setupTextField() + setupTextContainer() + setupButton(minimumValueButton, value: minimumValue, tag: .min) + minimumValueButton.contentHorizontalAlignment = .left + setupButton(maximumValueButton, value: maximumValue, tag: .max) + maximumValueButton.contentHorizontalAlignment = .right + setupUpperView() + setupSlider() + setupContainerView() + setupSelf() } - @IBAction func sliderValueChanged() { - guard value != Double(slider.value) else { return } - value = Double(slider.value) + func setupFormatter() { + formatter.allowsFloats = true + formatter.minimumIntegerDigits = 1 + formatter.minimumFractionDigits = 1 + formatter.maximumFractionDigits = 2 + } + + func setupLabel() { + label.font = textFont + label.text = title + label.textAlignment = .right + } + + func setupTextField() { + textField.text = format(value: initialValue) + textField.font = textFont + textField.textAlignment = .left + textField.delegate = self + } + + func setupTextContainer() { + textContainer.axis = .horizontal + textContainer.alignment = .firstBaseline + textContainer.distribution = .fill + textContainer.spacing = 8 + upperView.addArrangedSubview(minimumValueButton) + textContainer.addArrangedSubview(label) + textContainer.addArrangedSubview(textField) + textContainer.addArrangedSubview(UIView()) + } + + var textContainerIntrinsicContentSize: CGSize { + var size = CGSize.zero + let labelS = label.intrinsicContentSize + let minPadW = textContainer.spacing + let fieldS = textField.intrinsicContentSize + size.width = minMaxButtonW + 3 * minPadW + labelS.width + fieldS.width + size.height = max(minMaxButtonH, labelS.height, fieldS.height) + return size + } + + func setupButton(_ button: UIButton, value: Double, tag: ButtonTag) { + button.addTarget(self, action: #selector(buttonTapped(btn:)), for: .touchUpInside) + button.translatesAutoresizingMaskIntoConstraints = false + button.widthAnchor.constraint(equalToConstant: minMaxButtonW).isActive = true + button.heightAnchor.constraint(equalToConstant: minMaxButtonH).isActive = true + button.titleLabel?.font = label.font + button.setTitleColor(label.textColor, for: .normal) + button.setTitle(format(value: sanitize(value: value)), for: .normal) + button.tag = tag.rawValue + } + + func setupUpperView() { + upperView.axis = .horizontal + upperView.alignment = .firstBaseline + upperView.distribution = .fill + upperView.spacing = 8 + upperView.addArrangedSubview(textContainer) + upperView.addArrangedSubview(maximumValueButton) + } + + var upperViewIntrinsicContentSize: CGSize { + var size = CGSize.zero + let textContainerS = textContainerIntrinsicContentSize + size.width = textContainerS.width + upperView.spacing + minMaxButtonW + size.height = max(textContainerS.height, minMaxButtonH) + return size + } + + func setupSlider() { + slider.addTarget(self, action: #selector(sliderValueChanged), for: .valueChanged) + slider.minimumValue = Float(minimumValue) + slider.maximumValue = Float(maximumValue) + slider.value = Float(initialValue) + } + + func setupContainerView() { + containerView.axis = .vertical + containerView.alignment = .fill + containerView.distribution = .fill + containerView.spacing = 0 + containerView.addArrangedSubview(upperView) + containerView.addArrangedSubview(slider) + } + + var containerViewIntrinsicContentSize: CGSize { + var size = CGSize.zero + let upperViewS = upperViewIntrinsicContentSize + let sliderS = slider.intrinsicContentSize + size.width = max(upperViewS.width, sliderS.width) + size.height = upperViewS.height + containerView.spacing + sliderS.height + return size + } + + func setupSelf() { + addSubview(containerView) + translatesAutoresizingMaskIntoConstraints = false + containerView.translatesAutoresizingMaskIntoConstraints = false + containerView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true + containerView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true + containerView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true } } @@ -87,14 +267,31 @@ extension SliderView: UITextFieldDelegate { } func textFieldDidEndEditing(_ textField: UITextField) { - guard let value = Double(textField.text ?? "0") else { - self.value = defaultValue + guard let value = Double(textField.text ?? "\(initialValue)") else { + self.value = initialValue return } self.value = value } } +private extension SliderView { + @objc func buttonTapped(btn: UIButton) { + guard let btnTag = ButtonTag(rawValue: btn.tag) else { return } + switch btnTag { + case .min: + self.value = Double(slider.minimumValue) + case .max: + self.value = Double(slider.maximumValue) + } + } + + @objc func sliderValueChanged() { + guard value != Double(slider.value) else { return } + self.value = Double(slider.value) + } +} + private extension SliderView { func set(value: Double) { let sanitisedValue = sanitize(value: value) @@ -104,10 +301,12 @@ private extension SliderView { } func sanitize(value: Double) -> Double { - let minValue = Double(slider.minimumValue) - let maxValue = Double(slider.maximumValue) - let value = min(maxValue, max(value, minValue)) - return Double(trunc(10 * value)) / 10 + let minimumValue = Double(slider.minimumValue) + let maximumValue = Double(slider.maximumValue) + let value = min(maximumValue, max(value, minimumValue)) + var k = maximumFractionDigits + var factor: Double = 1; while k > 0 { factor *= 10; k -= 1 } + return Double(trunc(factor * value)) / factor } func format(value: Double) -> String? { diff --git a/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresenterVC.storyboard b/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresenterVC.storyboard index 5fed68d..b3a5c12 100644 --- a/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresenterVC.storyboard +++ b/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresenterVC.storyboard @@ -1,288 +1,706 @@ - + + + + - + + - + - + - - + + - - + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/SliderView.xib b/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/SliderView.xib deleted file mode 100644 index 8d0860e..0000000 --- a/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/SliderView.xib +++ /dev/null @@ -1,187 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DrawerKitDemo/DrawerKitDemo/SwitchView.swift b/DrawerKitDemo/DrawerKitDemo/SwitchView.swift new file mode 100644 index 0000000..bbb11e1 --- /dev/null +++ b/DrawerKitDemo/DrawerKitDemo/SwitchView.swift @@ -0,0 +1,168 @@ +import UIKit + +@IBDesignable +class SwitchView: UIControl { + let label = UILabel() + let toggler = UISwitch() + + @IBInspectable var title: String? { + get { return label.text } + set { label.text = newValue } + } + + @IBInspectable var textColor: UIColor { + get { return label.textColor } + set { label.textColor = newValue } + } + + @IBInspectable var isOn: Bool { + get { return toggler.isOn } + set { toggler.isOn = newValue } + } + + @IBInspectable var onTint: UIColor? { + get { return toggler.onTintColor } + set { toggler.onTintColor = newValue } + } + + @IBInspectable var thumbTint: UIColor? { + get { return toggler.thumbTintColor } + set { toggler.thumbTintColor = newValue } + } + + @IBInspectable var indented: Bool { + get { return !indentationView.isHidden } + set { indentationView.isHidden = !newValue } + } + + @IBInspectable var indentation: CGFloat { + get { return indentationConstraint?.constant ?? 0 } + set { + indentationConstraint?.constant = newValue + layoutIfNeeded() + } + } + + init(title: String? = nil, + initiallyOn: Bool = true, + indented: Bool = false, + indentation: CGFloat = 20) { + super.init(frame: .zero) + self.title = title + self.isOn = initiallyOn + self.indented = indented + self.indentation = indentation + setup() + } + + override init(frame: CGRect) { + super.init(frame: frame) + setup() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setup() + } + + override var intrinsicContentSize: CGSize { + var size = CGSize.zero + let labelS = label.intrinsicContentSize + let togglerS = toggler.intrinsicContentSize + let indentW = (indented ? indentation : 0) + size.width = indentW + labelS.width + minPadViewW + togglerS.width + size.height = max(labelS.height, togglerS.height) + return size + } + + override var isEnabled: Bool { + didSet { toggler.isEnabled = isEnabled } + } + + override func addTarget(_ target: Any?, + action: Selector, + for controlEvents: UIControlEvents) { + toggler.addTarget(target, action: action, for: controlEvents) + } + + override func removeTarget(_ target: Any?, + action: Selector?, + for controlEvents: UIControlEvents) { + toggler.removeTarget(target, action: action, for: controlEvents) + } + + private let containerView = UIStackView() + private let padViewL = UIView() + private let padViewR = UIView() + private let minPadViewW: CGFloat = 8 + private let indentationView = UIView() + private var indentationConstraint: NSLayoutConstraint! + private let textFont = UIFont.systemFont(ofSize: 15) +} + +private extension SwitchView { + func setup() { + toggler.isOn = isOn + setupLabel() + setupContainerView() + setupSelf() + setupConstraints() + } + + func setupLabel() { + label.font = textFont + label.textAlignment = .left + label.text = title + } + + func setupContainerView() { + containerView.axis = .horizontal + containerView.alignment = .center + containerView.distribution = .fill + containerView.spacing = 0 + containerView.addArrangedSubview(indentationView) + containerView.addArrangedSubview(label) + containerView.addArrangedSubview(padViewL) + containerView.addArrangedSubview(toggler) + containerView.addArrangedSubview(padViewR) + } + + func setupSelf() { + backgroundColor = .clear + addSubview(containerView) + } + + func setupConstraints() { + setupIndentationViewConstraints() + setupPadViewLConstraints() + setupPadViewRConstraints() + setupContainerViewConstraints() + } + + func setupIndentationViewConstraints() { + indentationView.translatesAutoresizingMaskIntoConstraints = false + indentationView.heightAnchor.constraint(equalToConstant: 5).isActive = true + indentationConstraint = indentationView.widthAnchor.constraint(equalToConstant: indentation) + indentationConstraint.isActive = true + indentationView.isHidden = !indented + } + + func setupPadViewLConstraints() { + padViewL.translatesAutoresizingMaskIntoConstraints = false + padViewL.widthAnchor.constraint(greaterThanOrEqualToConstant: minPadViewW).isActive = true + padViewL.heightAnchor.constraint(equalToConstant: 5).isActive = true + } + + func setupPadViewRConstraints() { + padViewR.translatesAutoresizingMaskIntoConstraints = false + padViewR.widthAnchor.constraint(equalToConstant: 2).isActive = true + } + + func setupContainerViewConstraints() { + translatesAutoresizingMaskIntoConstraints = false + containerView.translatesAutoresizingMaskIntoConstraints = false + containerView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true + containerView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true + containerView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true + } +} From 63233cdd438b7babdf774d0a97ed0c6a0490cc9f Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Sun, 15 Oct 2017 14:25:43 +0100 Subject: [PATCH 44/85] Added control for manipulating cubic Bezier control points, CubicBezierView. --- .../DrawerKitDemo.xcodeproj/project.pbxproj | 4 + .../DrawerKitDemo/CubicBezierView.swift | 299 ++++++++++++++++++ 2 files changed, 303 insertions(+) create mode 100644 DrawerKitDemo/DrawerKitDemo/CubicBezierView.swift diff --git a/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj b/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj index 8068163..2eac1a9 100644 --- a/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj +++ b/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ CB2CB7B71F8EEDF400AA152D /* SliderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7B61F8EEDF400AA152D /* SliderView.swift */; }; + CB3E51341F93528D00406C6A /* CubicBezierView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB3E51331F93528D00406C6A /* CubicBezierView.swift */; }; CB7E08D11F8F9B9D004ACB07 /* SwitchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB7E08D01F8F9B9D004ACB07 /* SwitchView.swift */; }; CBBA2D3A1F8E807400E0137F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBA2D391F8E807400E0137F /* AppDelegate.swift */; }; CBBA2D411F8E807400E0137F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CBBA2D3F1F8E807400E0137F /* Main.storyboard */; }; @@ -39,6 +40,7 @@ /* Begin PBXFileReference section */ CB2CB7B61F8EEDF400AA152D /* SliderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SliderView.swift; sourceTree = ""; }; + CB3E51331F93528D00406C6A /* CubicBezierView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CubicBezierView.swift; sourceTree = ""; }; CB7E08D01F8F9B9D004ACB07 /* SwitchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchView.swift; sourceTree = ""; }; CBBA2D361F8E807400E0137F /* DrawerKitDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DrawerKitDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; CBBA2D391F8E807400E0137F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -94,6 +96,7 @@ CBBA2D6D1F8E83E300E0137F /* PresentedView.swift */, CB2CB7B61F8EEDF400AA152D /* SliderView.swift */, CB7E08D01F8F9B9D004ACB07 /* SwitchView.swift */, + CB3E51331F93528D00406C6A /* CubicBezierView.swift */, CBBA2D421F8E807400E0137F /* Assets.xcassets */, CBBA2D6A1F8E839200E0137F /* Storyboards */, CBBA2D471F8E807400E0137F /* Info.plist */, @@ -189,6 +192,7 @@ files = ( CBBA2D3A1F8E807400E0137F /* AppDelegate.swift in Sources */, CBBA2D721F8E83E300E0137F /* PresentedViewController.swift in Sources */, + CB3E51341F93528D00406C6A /* CubicBezierView.swift in Sources */, CBBA2D711F8E83E300E0137F /* PresentedView.swift in Sources */, CB7E08D11F8F9B9D004ACB07 /* SwitchView.swift in Sources */, CB2CB7B71F8EEDF400AA152D /* SliderView.swift in Sources */, diff --git a/DrawerKitDemo/DrawerKitDemo/CubicBezierView.swift b/DrawerKitDemo/DrawerKitDemo/CubicBezierView.swift new file mode 100644 index 0000000..a5fdee4 --- /dev/null +++ b/DrawerKitDemo/DrawerKitDemo/CubicBezierView.swift @@ -0,0 +1,299 @@ +import UIKit + +protocol CubicBezierViewDelegate: class { + func cubicBezierView(_ view: CubicBezierView, + controlPoint1: CGPoint, + controlPoint2: CGPoint) +} + +@IBDesignable +class CubicBezierView: UIControl, HandleViewDelegate { + weak var delegate: CubicBezierViewDelegate? + private var renderer = Renderer() + private var handleAtZeroZero: HandleView! + private var handleAtOneOne: HandleView! + private var actualW: CGFloat = 0 + private var actualH: CGFloat = 0 + private var runningUnderIB = false + private let fakeSize: CGFloat = 250 + private let dimmingView = UIView() + + @IBInspectable var fractionalRadius: CGFloat = 0.6 { + didSet { setup(); setNeedsDisplay() } + } + + @IBInspectable var curveLineWidth: CGFloat = 5 { + didSet { renderer.curveLineWidth = curveLineWidth; setNeedsDisplay() } + } + + @IBInspectable var curveLineColor: UIColor = .black { + didSet { renderer.curveLineColor = curveLineColor; setNeedsDisplay() } + } + + @IBInspectable var handleSize: CGFloat = 25 { + didSet { setup(); renderer.handleSize = handleSize; setNeedsDisplay() } + } + + @IBInspectable var handleLineWidth: CGFloat = 2 { + didSet { renderer.handleLineWidth = handleLineWidth; setNeedsDisplay() } + } + + @IBInspectable var handleLineColor: UIColor = .white { + didSet { renderer.handleLineColor = handleLineColor; setNeedsDisplay() } + } + + @IBInspectable var handleBorderWidth: CGFloat = 3 { + didSet { renderer.handleBorderWidth = handleBorderWidth; setNeedsDisplay() } + } + + @IBInspectable var handleBorderColor: UIColor = .white { + didSet { renderer.handleBorderColor = handleBorderColor; setNeedsDisplay() } + } + + @IBInspectable var handleInteriorColor: UIColor = .lightGray { + didSet { renderer.handleInteriorColor = handleInteriorColor; setNeedsDisplay() } + } + + override init(frame: CGRect) { + super.init(frame: frame) + computeActualSize() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + + override func draw(_ rect: CGRect) { + renderer.renderCurve() + renderer.renderControls() + } + + override var isEnabled: Bool { + didSet { + if isEnabled { + dimmingView.removeFromSuperview() + isUserInteractionEnabled = true + } else { + addSubview(dimmingView) + dimmingView.frame = bounds + dimmingView.backgroundColor = UIColor.black.withAlphaComponent(0.15) + isUserInteractionEnabled = false + } + } + } + + fileprivate func handleViewDidMove(_ view: HandleView) { + switch view { + case handleAtZeroZero: + renderer.frame1 = view.frame + case handleAtOneOne: + renderer.frame2 = view.frame + default: + return + } + + delegate?.cubicBezierView(self, + controlPoint1: handleAtZeroZero.center / actualW, + controlPoint2: handleAtOneOne.center / actualW) + + setNeedsDisplay() + } + + override var frame: CGRect { + didSet { computeActualSize() } + } + + private func setup() { + var pointAtZeroZero = CGPoint( + x: (bounds.size.width - actualW) / 2, + y: (bounds.size.height - actualH) / 2 + actualH + ) + + // This is needed because IBDesignable doesn't wait for layout. + if actualW == 0 || actualH == 0 { + runningUnderIB = true + actualW = fakeSize + actualH = fakeSize + pointAtZeroZero = CGPoint(x: 0, y: actualH) + } + + if handleAtZeroZero == nil { + handleAtZeroZero = HandleView(anchor: pointAtZeroZero, + inverted: false, + delegate: self) + addSubview(handleAtZeroZero) + } + handleAtZeroZero.fractionalRadius = fractionalRadius + handleAtZeroZero.frame.size.width = handleSize + handleAtZeroZero.frame.size.height = handleSize + handleAtZeroZero.center.x = pointAtZeroZero.x + fractionalRadius * actualW + handleAtZeroZero.center.y = pointAtZeroZero.y + + var pointAtOneOne = pointAtZeroZero + pointAtOneOne.x += actualW + pointAtOneOne.y -= actualH + if handleAtOneOne == nil { + handleAtOneOne = HandleView(anchor: pointAtOneOne, + inverted: true, + delegate: self) + addSubview(handleAtOneOne) + } + handleAtOneOne.fractionalRadius = fractionalRadius + handleAtOneOne.frame.size.width = handleSize + handleAtOneOne.frame.size.height = handleSize + handleAtOneOne.center.x = pointAtOneOne.x - fractionalRadius * actualW + handleAtOneOne.center.y = pointAtOneOne.y + + if runningUnderIB { + let offsetY1: CGFloat = (-30.0/250.0) * fakeSize + handleAtZeroZero.setCenter(for: offsetY1) + let offsetY2: CGFloat = (30.0/250.0) * fakeSize + handleAtOneOne.setCenter(for: offsetY2) + } + + updateRenderer() + } + + private func updateRenderer() { + renderer.anchor1 = handleAtZeroZero.anchor + renderer.frame1 = handleAtZeroZero.frame + renderer.anchor2 = handleAtOneOne.anchor + renderer.frame2 = handleAtOneOne.frame + } + + private func computeActualSize() { + actualW = min(frame.size.width, frame.size.height) + actualH = actualW + setup() + setNeedsDisplay() + } +} + +private protocol HandleViewDelegate: class { + func handleViewDidMove(_ view: HandleView) +} + +private class HandleView: UIView { + let anchor: CGPoint + let inverted: Bool + var fractionalRadius: CGFloat? + weak var delegate: HandleViewDelegate? + + init(anchor: CGPoint, inverted: Bool, delegate: HandleViewDelegate?) { + self.anchor = anchor + self.inverted = inverted + self.delegate = delegate + super.init(frame: CGRect.zero) + setup() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override var center: CGPoint { + didSet { delegate?.handleViewDidMove(self) } + } + + func setup() { + backgroundColor = .clear + let gr = UIPanGestureRecognizer(target: self, action: #selector(handleDrag(gr:))) + addGestureRecognizer(gr) + } + + @objc + func handleDrag(gr: UIPanGestureRecognizer) { + let offset = gr.translation(in: gr.view?.superview) + gr.setTranslation(.zero, in: gr.view?.superview) + switch gr.state { + case .changed: + setCenter(for: offset.y) + default: + break + } + } + + func setCenter(for offsetY: CGFloat) { + guard let superview = superview else { return } + guard let fractionalRadius = fractionalRadius else { return } + let s: CGFloat = (inverted ? -1 : 1) + var pos = center + pos.y += offsetY + let dy = s * (pos.y - anchor.y) + guard dy <= 0 else { return } + let svW = superview.bounds.size.width + let svH = superview.bounds.size.height + let radius = fractionalRadius * min(svW, svH) + let rsq = radius * radius + let dxsq = rsq - dy * dy + guard dxsq >= 0 else { return } + let dx = s * sqrt(dxsq) + pos.x = (anchor.x + dx) + center = pos + } +} + +private struct Renderer { + var curveLineWidth: CGFloat = 5 + var curveLineColor: UIColor = .black + + var handleSize: CGFloat = 25 + var handleInteriorColor: UIColor = .lightGray + + var handleLineWidth: CGFloat = 2 + var handleLineColor: UIColor = .white + + var handleBorderWidth: CGFloat = 3 + var handleBorderColor: UIColor = .white + + var anchor1: CGPoint = .zero + var frame1: CGRect = .zero + var center1: CGPoint { return center(frame1) } + + var anchor2: CGPoint = .zero + var frame2: CGRect = .zero + var center2: CGPoint { return center(frame2) } + + func renderControls() { + renderControl(anchor: anchor1, frame: frame1) + renderControl(anchor: anchor2, frame: frame2) + } + + func renderCurve() { + curveLineColor.setStroke() + let path = UIBezierPath() + path.move(to: anchor1) + path.addCurve(to: anchor2, controlPoint1: center1, controlPoint2: center2) + path.lineWidth = curveLineWidth + path.stroke() + } + + func renderControl(anchor: CGPoint, frame: CGRect) { + handleLineColor.setStroke() + let path = UIBezierPath() + path.move(to: anchor) + path.addLine(to: center(frame)) + path.lineWidth = handleLineWidth + path.stroke() + + handleBorderColor.setFill() + UIBezierPath(ovalIn: frame).fill() + handleInteriorColor.setFill() + let insetRect = frame.insetBy(dx: handleBorderWidth, dy: handleBorderWidth) + UIBezierPath(ovalIn: insetRect).fill() + } + + func center(_ frame: CGRect) -> CGPoint { + var c = frame.origin + c.x += frame.size.width / 2 + c.y += frame.size.height / 2 + return c + } +} + +private extension CGPoint { + static func /(lhs: CGPoint, rhs: CGFloat) -> CGPoint { + return CGPoint(x: lhs.x / rhs, y: lhs.y / rhs) + } +} + From 18336253a82359ecb06b50307753ac664146ccf9 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Sun, 15 Oct 2017 14:26:16 +0100 Subject: [PATCH 45/85] Added all the controls to the presenter VC. --- .../Base.lproj/PresenterVC.storyboard | 116 ++++++++++++++---- 1 file changed, 95 insertions(+), 21 deletions(-) diff --git a/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresenterVC.storyboard b/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresenterVC.storyboard index b3a5c12..2e3f272 100644 --- a/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresenterVC.storyboard +++ b/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresenterVC.storyboard @@ -75,10 +75,10 @@ - + - + @@ -146,7 +146,7 @@ - + @@ -166,7 +166,7 @@ - + @@ -181,7 +181,7 @@ - + @@ -216,7 +216,7 @@ - + @@ -230,7 +230,7 @@ - + @@ -265,7 +265,7 @@ - + @@ -303,7 +303,7 @@ - + @@ -376,7 +376,7 @@ - + @@ -411,7 +411,7 @@ - + @@ -444,7 +444,7 @@ - + @@ -460,7 +460,7 @@ - + @@ -509,7 +509,7 @@ - + @@ -550,15 +550,50 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -591,7 +626,7 @@ - + @@ -600,7 +635,7 @@ - + @@ -641,14 +676,14 @@ - + - + @@ -684,7 +719,46 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From bb77e30110ebf2515d91ab6a18538c506602a58a Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Sun, 15 Oct 2017 14:26:47 +0100 Subject: [PATCH 46/85] Fixed a TODO comment. --- DrawerKit/DrawerKit/Public API/DrawerDisplayController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DrawerKit/DrawerKit/Public API/DrawerDisplayController.swift b/DrawerKit/DrawerKit/Public API/DrawerDisplayController.swift index 7ca42d9..2edab97 100755 --- a/DrawerKit/DrawerKit/Public API/DrawerDisplayController.swift +++ b/DrawerKit/DrawerKit/Public API/DrawerDisplayController.swift @@ -2,8 +2,8 @@ import UIKit // TODO: // - support device interface orientation changes -// - support fixed height content -// - support not-covering status bar and/or gap at top +// - support insufficiently tall content +// - support not-covering status bar and/or having a gap at the top public final class DrawerDisplayController: NSObject { public let configuration: DrawerConfiguration // intentionally immutable From ab99b5e0618e727838467fb21ac9a5e945defc7b Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Sun, 15 Oct 2017 14:31:17 +0100 Subject: [PATCH 47/85] Make sure that durationInSeconds is a positive value. --- DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift b/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift index 7222cf8..35f843b 100644 --- a/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift +++ b/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift @@ -32,7 +32,7 @@ public struct DrawerConfiguration: Equatable { upperMarkGap: CGFloat = 40, lowerMarkGap: CGFloat = 40, maximumCornerRadius: CGFloat = 15) { - self.durationInSeconds = durationInSeconds + self.durationInSeconds = (durationInSeconds > 0 ? durationInSeconds : 0.8) self.timingCurveProvider = timingCurveProvider self.coversStatusBar = coversStatusBar self.supportsPartialExpansion = supportsPartialExpansion From 2ec596dad7ed5b29e8680f940e2eb9fc4f0b71c4 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Sun, 15 Oct 2017 14:33:23 +0100 Subject: [PATCH 48/85] Removed all references to coversStatusBar since that hasn't been implemented yet. --- .../Extensions/DrawerConfiguration+Equatable.swift | 1 - DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift | 3 --- .../Public API/DrawerDisplayController+Configuration.swift | 4 ---- DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift | 6 ------ 4 files changed, 14 deletions(-) diff --git a/DrawerKit/DrawerKit/Internal API/Extensions/DrawerConfiguration+Equatable.swift b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerConfiguration+Equatable.swift index c447137..f18fb33 100644 --- a/DrawerKit/DrawerKit/Internal API/Extensions/DrawerConfiguration+Equatable.swift +++ b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerConfiguration+Equatable.swift @@ -4,7 +4,6 @@ extension DrawerConfiguration { public static func ==(lhs: DrawerConfiguration, rhs: DrawerConfiguration) -> Bool { return lhs.durationInSeconds == rhs.durationInSeconds && lhs.timingCurveProvider === rhs.timingCurveProvider - && lhs.coversStatusBar == rhs.coversStatusBar && lhs.supportsPartialExpansion == rhs.supportsPartialExpansion && lhs.dismissesInStages == rhs.dismissesInStages && lhs.isDrawerDraggable == rhs.isDrawerDraggable diff --git a/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift b/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift index 35f843b..f681eb0 100644 --- a/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift +++ b/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift @@ -5,7 +5,6 @@ public struct DrawerConfiguration: Equatable { public var durationInSeconds: TimeInterval public var timingCurveProvider: UITimingCurveProvider - public var coversStatusBar: Bool public var supportsPartialExpansion: Bool public var dismissesInStages: Bool public var isDrawerDraggable: Bool @@ -22,7 +21,6 @@ public struct DrawerConfiguration: Equatable { public init(durationInSeconds: TimeInterval = 0.8, timingCurveProvider: UITimingCurveProvider = UISpringTimingParameters(), - coversStatusBar: Bool = true, supportsPartialExpansion: Bool = true, dismissesInStages: Bool = true, isDrawerDraggable: Bool = true, @@ -34,7 +32,6 @@ public struct DrawerConfiguration: Equatable { maximumCornerRadius: CGFloat = 15) { self.durationInSeconds = (durationInSeconds > 0 ? durationInSeconds : 0.8) self.timingCurveProvider = timingCurveProvider - self.coversStatusBar = coversStatusBar self.supportsPartialExpansion = supportsPartialExpansion self.dismissesInStages = dismissesInStages self.isDrawerDraggable = isDrawerDraggable diff --git a/DrawerKit/DrawerKit/Public API/DrawerDisplayController+Configuration.swift b/DrawerKit/DrawerKit/Public API/DrawerDisplayController+Configuration.swift index 7cc2220..046019f 100755 --- a/DrawerKit/DrawerKit/Public API/DrawerDisplayController+Configuration.swift +++ b/DrawerKit/DrawerKit/Public API/DrawerDisplayController+Configuration.swift @@ -11,10 +11,6 @@ extension DrawerDisplayController { return configuration.timingCurveProvider } - public var coversStatusBar: Bool { - return configuration.coversStatusBar - } - public var supportsPartialExpansion: Bool { return configuration.supportsPartialExpansion } diff --git a/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift b/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift index dd29c8f..3beb703 100644 --- a/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift +++ b/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift @@ -15,7 +15,6 @@ class PresenterViewController: UIViewController, DrawerPresenting { private var durationInSeconds: CGFloat = 0.8 private var hasFixedHeight = false - private var coversStatusBar = true private var supportsPartialExpansion = true private var dismissesInStages = true private var isDrawerDraggable = true @@ -27,7 +26,6 @@ class PresenterViewController: UIViewController, DrawerPresenting { private var maximumCornerRadius: CGFloat = 30 @IBOutlet weak var hasFixedHeightSwitch: UISwitch! - @IBOutlet weak var coversStatusBarSwitch: UISwitch! @IBOutlet weak var supportsPartialExpansionSwitch: UISwitch! @IBOutlet weak var dismissesInStagesSwitch: UISwitch! @IBOutlet weak var drawerDraggableSwitch: UISwitch! @@ -58,7 +56,6 @@ extension PresenterViewController { // ... or after initialisation configuration.durationInSeconds = 0.8 // TimeInterval(durationSliderView.value) configuration.timingCurveProvider = UISpringTimingParameters(dampingRatio: 0.8) - configuration.coversStatusBar = coversStatusBar configuration.supportsPartialExpansion = supportsPartialExpansion configuration.dismissesInStages = dismissesInStages configuration.isDrawerDraggable = isDrawerDraggable @@ -113,7 +110,6 @@ private extension PresenterViewController { // title: "Max corner radius", minValue: 0, maxValue: 30, // initialValue: 15, defaultValue: 15) // hasFixedHeightSwitch.isOn = hasFixedHeight -// coversStatusBarSwitch.isOn = coversStatusBar // supportsPartialExpansionSwitch.isOn = supportsPartialExpansion // dismissesInStagesSwitch.isEnabled = supportsPartialExpansion // dismissesInStagesSwitch.isOn = dismissesInStages @@ -125,8 +121,6 @@ private extension PresenterViewController { switch toggler { case hasFixedHeightSwitch: hasFixedHeight = toggler.isOn - case coversStatusBarSwitch: - coversStatusBar = toggler.isOn case supportsPartialExpansionSwitch: supportsPartialExpansion = toggler.isOn dismissesInStagesSwitch.isEnabled = toggler.isOn From 7b2a304296a8edc14c3c1ad9f08f7ae67adeb9d8 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Sun, 15 Oct 2017 14:38:19 +0100 Subject: [PATCH 49/85] Make sure that flickSpeedThreshold is a non-negative value. Also, if zero, disables support for flicking. --- DrawerKit/DrawerKit/Internal API/PresentationController.swift | 3 ++- DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/DrawerKit/DrawerKit/Internal API/PresentationController.swift b/DrawerKit/DrawerKit/Internal API/PresentationController.swift index 5ac26b8..c247752 100644 --- a/DrawerKit/DrawerKit/Internal API/PresentationController.swift +++ b/DrawerKit/DrawerKit/Internal API/PresentationController.swift @@ -239,7 +239,8 @@ private extension PresentationController { let isNotMoving = (velocityY == 0) let isMovingUp = (velocityY < 0) // recall that Y-axis points down let isMovingDown = (velocityY > 0) - let isMovingQuickly = (abs(velocityY) > flickSpeedThreshold) + // flickSpeedThreshold == 0 disables speed-dependence + let isMovingQuickly = (flickSpeedThreshold > 0) && (abs(velocityY) > flickSpeedThreshold) let isMovingUpQuickly = isMovingUp && isMovingQuickly let isMovingDownQuickly = isMovingDown && isMovingQuickly let isAboveUpperMark = (positionY < upperMarkY) diff --git a/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift b/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift index f681eb0..62e6c23 100644 --- a/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift +++ b/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift @@ -37,7 +37,7 @@ public struct DrawerConfiguration: Equatable { self.isDrawerDraggable = isDrawerDraggable self.isDismissableByOutsideDrawerTaps = isDismissableByOutsideDrawerTaps self.numberOfTapsForOutsideDrawerDismissal = numberOfTapsForOutsideDrawerDismissal - self.flickSpeedThreshold = flickSpeedThreshold + self.flickSpeedThreshold = max(0, flickSpeedThreshold) self.upperMarkGap = upperMarkGap self.lowerMarkGap = lowerMarkGap self.maximumCornerRadius = maximumCornerRadius From 355d67ae1a4f1d8cfcf08c047b02a5369c0087b4 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Sun, 15 Oct 2017 14:41:32 +0100 Subject: [PATCH 50/85] Make sure that upperMarkGap and lowerMarkGap are non-negative values. --- DrawerKit/DrawerKit/Internal API/PresentationController.swift | 2 +- DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DrawerKit/DrawerKit/Internal API/PresentationController.swift b/DrawerKit/DrawerKit/Internal API/PresentationController.swift index c247752..beb07ab 100644 --- a/DrawerKit/DrawerKit/Internal API/PresentationController.swift +++ b/DrawerKit/DrawerKit/Internal API/PresentationController.swift @@ -71,7 +71,7 @@ private extension PresentationController { } var upperMarkY: CGFloat { - return (containerViewH - drawerPartialH) - upperMarkGap + return drawerPartialY - upperMarkGap } var lowerMarkY: CGFloat { diff --git a/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift b/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift index 62e6c23..b6b2562 100644 --- a/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift +++ b/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift @@ -38,8 +38,8 @@ public struct DrawerConfiguration: Equatable { self.isDismissableByOutsideDrawerTaps = isDismissableByOutsideDrawerTaps self.numberOfTapsForOutsideDrawerDismissal = numberOfTapsForOutsideDrawerDismissal self.flickSpeedThreshold = max(0, flickSpeedThreshold) - self.upperMarkGap = upperMarkGap - self.lowerMarkGap = lowerMarkGap + self.upperMarkGap = max(0, upperMarkGap) + self.lowerMarkGap = max(0, lowerMarkGap) self.maximumCornerRadius = maximumCornerRadius } } From a3f545b638f673960e5e59ae0564fb65c615aa0e Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Sun, 15 Oct 2017 14:48:03 +0100 Subject: [PATCH 51/85] Only add debugging mark lines if at least one of upperMarkGap and lowerMarkGap is a positive value. --- .../Internal API/PresentationController.swift | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/DrawerKit/DrawerKit/Internal API/PresentationController.swift b/DrawerKit/DrawerKit/Internal API/PresentationController.swift index beb07ab..ee9a18b 100644 --- a/DrawerKit/DrawerKit/Internal API/PresentationController.swift +++ b/DrawerKit/DrawerKit/Internal API/PresentationController.swift @@ -284,18 +284,23 @@ private extension PresentationController { func setupDebugHeightMarks() { guard inDebugMode else { return } guard let containerView = containerView else { return } + guard upperMarkGap > 0 || lowerMarkGap > 0 else { return } + + if upperMarkGap > 0 { + let upperMarkYView = UIView() + upperMarkYView.backgroundColor = .black + upperMarkYView.frame = CGRect(x: 0, y: upperMarkY, + width: containerView.bounds.size.width, height: 3) + containerView.addSubview(upperMarkYView) + } - let upperMarkYView = UIView() - upperMarkYView.backgroundColor = .black - upperMarkYView.frame = CGRect(x: 0, y: upperMarkY, - width: containerView.bounds.size.width, height: 3) - containerView.addSubview(upperMarkYView) - - let lowerMarkYView = UIView() - lowerMarkYView.backgroundColor = .black - lowerMarkYView.frame = CGRect(x: 0, y: lowerMarkY, - width: containerView.bounds.size.width, height: 3) - containerView.addSubview(lowerMarkYView) + if lowerMarkGap > 0 { + let lowerMarkYView = UIView() + lowerMarkYView.backgroundColor = .black + lowerMarkYView.frame = CGRect(x: 0, y: lowerMarkY, + width: containerView.bounds.size.width, height: 3) + containerView.addSubview(lowerMarkYView) + } let drawerMarkView = UIView() drawerMarkView.backgroundColor = .white From 7efb1333accc49ba97d760eb26fce45ece4d46c0 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Sun, 15 Oct 2017 14:50:29 +0100 Subject: [PATCH 52/85] Make sure that maximumCornerRadius is a non-negative value. --- DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift b/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift index b6b2562..844133e 100644 --- a/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift +++ b/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift @@ -40,6 +40,6 @@ public struct DrawerConfiguration: Equatable { self.flickSpeedThreshold = max(0, flickSpeedThreshold) self.upperMarkGap = max(0, upperMarkGap) self.lowerMarkGap = max(0, lowerMarkGap) - self.maximumCornerRadius = maximumCornerRadius + self.maximumCornerRadius = max(0, maximumCornerRadius) } } From 01b1974e86963def4d10945558f192b8ab60b373 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Sun, 15 Oct 2017 14:57:18 +0100 Subject: [PATCH 53/85] Only animate the corner radius if maximumCornerRadius is strictly positive. --- .../Internal API/PresentationController.swift | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/DrawerKit/DrawerKit/Internal API/PresentationController.swift b/DrawerKit/DrawerKit/Internal API/PresentationController.swift index ee9a18b..40bae05 100644 --- a/DrawerKit/DrawerKit/Internal API/PresentationController.swift +++ b/DrawerKit/DrawerKit/Internal API/PresentationController.swift @@ -29,7 +29,9 @@ extension PresentationController { setupContainerViewDismissalTapRecogniser() setupPresentedViewDragRecogniser() setupDebugHeightMarks() - addCornerRadiusAnimationEnding(at: drawerPartialY) + if maximumCornerRadius > 0 { + addCornerRadiusAnimationEnding(at: drawerPartialY) + } } override func presentationTransitionDidEnd(_ completed: Bool) { @@ -39,7 +41,9 @@ extension PresentationController { } override func dismissalTransitionWillBegin() { - addCornerRadiusAnimationEnding(at: containerViewH) + if maximumCornerRadius > 0 { + addCornerRadiusAnimationEnding(at: containerViewH) + } } override func dismissalTransitionDidEnd(_ completed: Bool) { @@ -146,7 +150,9 @@ private extension PresentationController { gr.setTranslation(.zero, in: view) let positionY = currentDrawerY + offsetY currentDrawerY = min(max(positionY, 0), containerViewH) - currentDrawerCornerRadius = cornerRadius(at: currentDrawerY) + if maximumCornerRadius > 0 { + currentDrawerCornerRadius = cornerRadius(at: currentDrawerY) + } case .ended: let drawerVelocityY = gr.velocity(in: view).y / containerViewH @@ -166,7 +172,9 @@ private extension PresentationController { private extension PresentationController { func animateTransition(to endPositionY: CGFloat, clamping: Bool = false) { addPositionAnimationEnding(at: endPositionY, clamping: clamping) - addCornerRadiusAnimationEnding(at: endPositionY, clamping: clamping) + if maximumCornerRadius > 0 { + addCornerRadiusAnimationEnding(at: endPositionY, clamping: clamping) + } } func addPositionAnimationEnding(at endPositionY: CGFloat, clamping: Bool = false) { @@ -192,6 +200,7 @@ private extension PresentationController { } func addCornerRadiusAnimationEnding(at endPositionY: CGFloat, clamping: Bool = false) { + guard maximumCornerRadius > 0 else { return } guard drawerPartialY > 0 else { return } guard endPositionY != currentDrawerY else { return } @@ -216,6 +225,7 @@ private extension PresentationController { } func cornerRadius(at positionY: CGFloat) -> CGFloat { + guard maximumCornerRadius > 0 else { return currentDrawerCornerRadius } guard drawerPartialY > 0 && drawerPartialY < containerViewH else { return 0 } guard positionY >= 0 && positionY <= containerViewH else { return 0 } From 53476c3ca88105014daea1c4ba1ca8a846a1465f Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Sun, 15 Oct 2017 14:58:49 +0100 Subject: [PATCH 54/85] Make sure that numberOfTapsForOutsideDrawerDismissal is a non-negative value. --- DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift b/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift index 844133e..ddb818f 100644 --- a/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift +++ b/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift @@ -36,7 +36,7 @@ public struct DrawerConfiguration: Equatable { self.dismissesInStages = dismissesInStages self.isDrawerDraggable = isDrawerDraggable self.isDismissableByOutsideDrawerTaps = isDismissableByOutsideDrawerTaps - self.numberOfTapsForOutsideDrawerDismissal = numberOfTapsForOutsideDrawerDismissal + self.numberOfTapsForOutsideDrawerDismissal = max(0, numberOfTapsForOutsideDrawerDismissal) self.flickSpeedThreshold = max(0, flickSpeedThreshold) self.upperMarkGap = max(0, upperMarkGap) self.lowerMarkGap = max(0, lowerMarkGap) From 3cabcd6451eb167fa963b7e1c45b7e3ebedf74c9 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Sun, 15 Oct 2017 16:20:34 +0100 Subject: [PATCH 55/85] Resolved an issue with animating rounding the presented view corners. --- .../Internal API/PresentationController.swift | 60 +++++++++++-------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/DrawerKit/DrawerKit/Internal API/PresentationController.swift b/DrawerKit/DrawerKit/Internal API/PresentationController.swift index 40bae05..b3a069f 100644 --- a/DrawerKit/DrawerKit/Internal API/PresentationController.swift +++ b/DrawerKit/DrawerKit/Internal API/PresentationController.swift @@ -29,21 +29,11 @@ extension PresentationController { setupContainerViewDismissalTapRecogniser() setupPresentedViewDragRecogniser() setupDebugHeightMarks() - if maximumCornerRadius > 0 { - addCornerRadiusAnimationEnding(at: drawerPartialY) - } - } - - override func presentationTransitionDidEnd(_ completed: Bool) { - if currentDrawerY == 0 || currentDrawerY == containerViewH { - currentDrawerCornerRadius = 0 - } + addCornerRadiusAnimationEnding(at: drawerPartialY) } override func dismissalTransitionWillBegin() { - if maximumCornerRadius > 0 { - addCornerRadiusAnimationEnding(at: containerViewH) - } + addCornerRadiusAnimationEnding(at: containerViewH) } override func dismissalTransitionDidEnd(_ completed: Bool) { @@ -89,7 +79,15 @@ private extension PresentationController { var currentDrawerCornerRadius: CGFloat { get { return presentedView?.layer.cornerRadius ?? 0 } - set { presentedView?.layer.cornerRadius = newValue } + set { + presentedView?.layer.cornerRadius = newValue + if #available(iOS 11.0, *) { + presentedView?.layer.maskedCorners = + [.layerMinXMinYCorner, .layerMaxXMinYCorner] + } else { + presentedView?.roundCorners([.topLeft, .topRight], radius: newValue) + } + } } } @@ -150,9 +148,7 @@ private extension PresentationController { gr.setTranslation(.zero, in: view) let positionY = currentDrawerY + offsetY currentDrawerY = min(max(positionY, 0), containerViewH) - if maximumCornerRadius > 0 { - currentDrawerCornerRadius = cornerRadius(at: currentDrawerY) - } + currentDrawerCornerRadius = cornerRadius(at: currentDrawerY) case .ended: let drawerVelocityY = gr.velocity(in: view).y / containerViewH @@ -171,13 +167,6 @@ private extension PresentationController { private extension PresentationController { func animateTransition(to endPositionY: CGFloat, clamping: Bool = false) { - addPositionAnimationEnding(at: endPositionY, clamping: clamping) - if maximumCornerRadius > 0 { - addCornerRadiusAnimationEnding(at: endPositionY, clamping: clamping) - } - } - - func addPositionAnimationEnding(at endPositionY: CGFloat, clamping: Bool = false) { guard endPositionY != currentDrawerY else { return } let endPosY = (clamping ? clamped(endPositionY) : endPositionY) @@ -186,8 +175,13 @@ private extension PresentationController { let animator = UIViewPropertyAnimator(duration: durationInSeconds, timingParameters: timingCurveProvider) + let maxCornerRadius = maximumCornerRadius + let endingCornerRadius = cornerRadius(at: endPosY) animator.addAnimations { [weak self] in self?.currentDrawerY = endPosY + if maxCornerRadius > 0 { + self?.currentDrawerCornerRadius = endingCornerRadius + } } if endPosY == containerViewH { @@ -196,6 +190,12 @@ private extension PresentationController { } } + if maxCornerRadius > 0 && endPosY != drawerPartialY { + animator.addCompletion { [weak self] _ in + self?.currentDrawerCornerRadius = 0 + } + } + animator.startAnimation() } @@ -215,7 +215,7 @@ private extension PresentationController { self?.currentDrawerCornerRadius = endingCornerRadius } - if endPosY == 0 || endPosY == containerViewH { + if endPosY != drawerPartialY { animator.addCompletion { [weak self] _ in self?.currentDrawerCornerRadius = 0 } @@ -319,3 +319,15 @@ private extension PresentationController { containerView.addSubview(drawerMarkView) } } + +// For versions of iOS lower than 11.0 +extension UIView { + func roundCorners(_ corners: UIRectCorner, radius: CGFloat) { + let path = UIBezierPath(roundedRect: self.bounds, + byRoundingCorners: corners, + cornerRadii: CGSize(width: radius, height: radius)) + let mask = CAShapeLayer() + mask.path = path.cgPath + self.layer.mask = mask + } +} From 412a27ea78481333825a006521a0c94505d96b57 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Sun, 15 Oct 2017 16:44:16 +0100 Subject: [PATCH 56/85] Simplified the demo app to its bare essentials. No more configuration controls to the wazoo. --- .../DrawerKitDemo.xcodeproj/project.pbxproj | 40 - .../AppIcon.appiconset/Contents.json | 30 + .../Assets.xcassets/Contents.json | 6 + .../Saturn.imageset/Contents.json | 21 + .../Saturn.imageset/Saturn.jpg | Bin 0 -> 64957 bytes .../close.imageset/Contents.json | 23 + ...een Shot 2017-10-15 at 16.24.27 copy 2.png | Bin 0 -> 27717 bytes ...creen Shot 2017-10-15 at 16.24.27 copy.png | Bin 0 -> 18879 bytes .../Assets.xcassets/close.imageset/close.png | Bin 0 -> 10029 bytes .../DrawerKitDemo/CubicBezierView.swift | 299 ------- .../DrawerKitDemo/PresentedView.swift | 1 + .../PresentedViewController.swift | 19 +- .../DrawerKitDemo/PresenterView.swift | 3 - .../PresenterViewController.swift | 143 +--- DrawerKitDemo/DrawerKitDemo/SliderView.swift | 316 ------- .../Storyboards/Base.lproj/Main.storyboard | 196 +++-- .../Base.lproj/PresentedVC.storyboard | 142 ---- .../Base.lproj/PresenterVC.storyboard | 780 ------------------ DrawerKitDemo/DrawerKitDemo/SwitchView.swift | 168 ---- 19 files changed, 242 insertions(+), 1945 deletions(-) create mode 100644 DrawerKitDemo/DrawerKitDemo/Assets.xcassets/Contents.json create mode 100644 DrawerKitDemo/DrawerKitDemo/Assets.xcassets/Saturn.imageset/Contents.json create mode 100644 DrawerKitDemo/DrawerKitDemo/Assets.xcassets/Saturn.imageset/Saturn.jpg create mode 100644 DrawerKitDemo/DrawerKitDemo/Assets.xcassets/close.imageset/Contents.json create mode 100644 DrawerKitDemo/DrawerKitDemo/Assets.xcassets/close.imageset/Screen Shot 2017-10-15 at 16.24.27 copy 2.png create mode 100644 DrawerKitDemo/DrawerKitDemo/Assets.xcassets/close.imageset/Screen Shot 2017-10-15 at 16.24.27 copy.png create mode 100644 DrawerKitDemo/DrawerKitDemo/Assets.xcassets/close.imageset/close.png delete mode 100644 DrawerKitDemo/DrawerKitDemo/CubicBezierView.swift delete mode 100644 DrawerKitDemo/DrawerKitDemo/PresenterView.swift delete mode 100644 DrawerKitDemo/DrawerKitDemo/SliderView.swift delete mode 100644 DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresentedVC.storyboard delete mode 100644 DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresenterVC.storyboard delete mode 100644 DrawerKitDemo/DrawerKitDemo/SwitchView.swift diff --git a/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj b/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj index 2eac1a9..1464b81 100644 --- a/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj +++ b/DrawerKitDemo/DrawerKitDemo.xcodeproj/project.pbxproj @@ -7,9 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - CB2CB7B71F8EEDF400AA152D /* SliderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CB7B61F8EEDF400AA152D /* SliderView.swift */; }; - CB3E51341F93528D00406C6A /* CubicBezierView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB3E51331F93528D00406C6A /* CubicBezierView.swift */; }; - CB7E08D11F8F9B9D004ACB07 /* SwitchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB7E08D01F8F9B9D004ACB07 /* SwitchView.swift */; }; CBBA2D3A1F8E807400E0137F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBA2D391F8E807400E0137F /* AppDelegate.swift */; }; CBBA2D411F8E807400E0137F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CBBA2D3F1F8E807400E0137F /* Main.storyboard */; }; CBBA2D431F8E807400E0137F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CBBA2D421F8E807400E0137F /* Assets.xcassets */; }; @@ -17,11 +14,8 @@ CBBA2D631F8E817400E0137F /* DrawerKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CBBA2D621F8E817400E0137F /* DrawerKit.framework */; }; CBBA2D641F8E817400E0137F /* DrawerKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CBBA2D621F8E817400E0137F /* DrawerKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; CBBA2D6F1F8E83E300E0137F /* PresenterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBA2D6B1F8E83E300E0137F /* PresenterViewController.swift */; }; - CBBA2D701F8E83E300E0137F /* PresenterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBA2D6C1F8E83E300E0137F /* PresenterView.swift */; }; CBBA2D711F8E83E300E0137F /* PresentedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBA2D6D1F8E83E300E0137F /* PresentedView.swift */; }; CBBA2D721F8E83E300E0137F /* PresentedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBA2D6E1F8E83E300E0137F /* PresentedViewController.swift */; }; - CBBA2D731F8E855C00E0137F /* PresenterVC.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CBBA2D661F8E836100E0137F /* PresenterVC.storyboard */; }; - CBBA2D741F8E856100E0137F /* PresentedVC.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CBBA2D681F8E836100E0137F /* PresentedVC.storyboard */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -39,9 +33,6 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - CB2CB7B61F8EEDF400AA152D /* SliderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SliderView.swift; sourceTree = ""; }; - CB3E51331F93528D00406C6A /* CubicBezierView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CubicBezierView.swift; sourceTree = ""; }; - CB7E08D01F8F9B9D004ACB07 /* SwitchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchView.swift; sourceTree = ""; }; CBBA2D361F8E807400E0137F /* DrawerKitDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DrawerKitDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; CBBA2D391F8E807400E0137F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; CBBA2D401F8E807400E0137F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; @@ -49,10 +40,7 @@ CBBA2D451F8E807400E0137F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; CBBA2D471F8E807400E0137F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; CBBA2D621F8E817400E0137F /* DrawerKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DrawerKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - CBBA2D671F8E836100E0137F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/PresenterVC.storyboard; sourceTree = ""; }; - CBBA2D691F8E836100E0137F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/PresentedVC.storyboard; sourceTree = ""; }; CBBA2D6B1F8E83E300E0137F /* PresenterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresenterViewController.swift; sourceTree = ""; }; - CBBA2D6C1F8E83E300E0137F /* PresenterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresenterView.swift; sourceTree = ""; }; CBBA2D6D1F8E83E300E0137F /* PresentedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentedView.swift; sourceTree = ""; }; CBBA2D6E1F8E83E300E0137F /* PresentedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentedViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -91,12 +79,8 @@ children = ( CBBA2D391F8E807400E0137F /* AppDelegate.swift */, CBBA2D6B1F8E83E300E0137F /* PresenterViewController.swift */, - CBBA2D6C1F8E83E300E0137F /* PresenterView.swift */, CBBA2D6E1F8E83E300E0137F /* PresentedViewController.swift */, CBBA2D6D1F8E83E300E0137F /* PresentedView.swift */, - CB2CB7B61F8EEDF400AA152D /* SliderView.swift */, - CB7E08D01F8F9B9D004ACB07 /* SwitchView.swift */, - CB3E51331F93528D00406C6A /* CubicBezierView.swift */, CBBA2D421F8E807400E0137F /* Assets.xcassets */, CBBA2D6A1F8E839200E0137F /* Storyboards */, CBBA2D471F8E807400E0137F /* Info.plist */, @@ -108,8 +92,6 @@ isa = PBXGroup; children = ( CBBA2D3F1F8E807400E0137F /* Main.storyboard */, - CBBA2D661F8E836100E0137F /* PresenterVC.storyboard */, - CBBA2D681F8E836100E0137F /* PresentedVC.storyboard */, CBBA2D441F8E807400E0137F /* LaunchScreen.storyboard */, ); path = Storyboards; @@ -175,10 +157,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - CBBA2D741F8E856100E0137F /* PresentedVC.storyboard in Resources */, CBBA2D461F8E807400E0137F /* LaunchScreen.storyboard in Resources */, CBBA2D431F8E807400E0137F /* Assets.xcassets in Resources */, - CBBA2D731F8E855C00E0137F /* PresenterVC.storyboard in Resources */, CBBA2D411F8E807400E0137F /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -192,12 +172,8 @@ files = ( CBBA2D3A1F8E807400E0137F /* AppDelegate.swift in Sources */, CBBA2D721F8E83E300E0137F /* PresentedViewController.swift in Sources */, - CB3E51341F93528D00406C6A /* CubicBezierView.swift in Sources */, CBBA2D711F8E83E300E0137F /* PresentedView.swift in Sources */, - CB7E08D11F8F9B9D004ACB07 /* SwitchView.swift in Sources */, - CB2CB7B71F8EEDF400AA152D /* SliderView.swift in Sources */, CBBA2D6F1F8E83E300E0137F /* PresenterViewController.swift in Sources */, - CBBA2D701F8E83E300E0137F /* PresenterView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -220,22 +196,6 @@ name = LaunchScreen.storyboard; sourceTree = ""; }; - CBBA2D661F8E836100E0137F /* PresenterVC.storyboard */ = { - isa = PBXVariantGroup; - children = ( - CBBA2D671F8E836100E0137F /* Base */, - ); - name = PresenterVC.storyboard; - sourceTree = ""; - }; - CBBA2D681F8E836100E0137F /* PresentedVC.storyboard */ = { - isa = PBXVariantGroup; - children = ( - CBBA2D691F8E836100E0137F /* Base */, - ); - name = PresentedVC.storyboard; - sourceTree = ""; - }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ diff --git a/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/AppIcon.appiconset/Contents.json b/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/AppIcon.appiconset/Contents.json index 36d2c80..d8db8d6 100644 --- a/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,5 +1,15 @@ { "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, { "idiom" : "iphone", "size" : "29x29", @@ -30,6 +40,16 @@ "size" : "60x60", "scale" : "3x" }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, { "idiom" : "ipad", "size" : "29x29", @@ -59,6 +79,16 @@ "idiom" : "ipad", "size" : "76x76", "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" } ], "info" : { diff --git a/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/Contents.json b/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/Saturn.imageset/Contents.json b/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/Saturn.imageset/Contents.json new file mode 100644 index 0000000..63d78bc --- /dev/null +++ b/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/Saturn.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Saturn.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/Saturn.imageset/Saturn.jpg b/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/Saturn.imageset/Saturn.jpg new file mode 100644 index 0000000000000000000000000000000000000000..704c2e6bd916dc8631ffd6f9d6f3123a1f1f60a4 GIT binary patch literal 64957 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

KHggrkAERd*3l^fF-P2~sKW&xt)`Zfe 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 Iv48*kA4D^ycK`qY literal 0 HcmV?d00001 diff --git a/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/close.imageset/Contents.json b/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/close.imageset/Contents.json new file mode 100644 index 0000000..bafd770 --- /dev/null +++ b/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/close.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "close.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Screen Shot 2017-10-15 at 16.24.27 copy.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "Screen Shot 2017-10-15 at 16.24.27 copy 2.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/close.imageset/Screen Shot 2017-10-15 at 16.24.27 copy 2.png b/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/close.imageset/Screen Shot 2017-10-15 at 16.24.27 copy 2.png new file mode 100644 index 0000000000000000000000000000000000000000..eafb8e511c48e042bbbfe6b99504ece301ba43fb GIT binary patch literal 27717 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 R?VreBQC3Z+QOZ2x{{UX(*Wv&G literal 0 HcmV?d00001 diff --git a/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/close.imageset/Screen Shot 2017-10-15 at 16.24.27 copy.png b/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/close.imageset/Screen Shot 2017-10-15 at 16.24.27 copy.png new file mode 100644 index 0000000000000000000000000000000000000000..9aac8d2d6396b94742f9dd76b7963608c3c04496 GIT binary patch literal 18879 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 z zWw{hfl8v1~UIDrBWQJo57NRdoq$wb!qF4C~3=n*VGW|4O8G$msViaKiuT z!ysYqfzkBqO3{PORrN81f!-VuEcg1S9`Q4oxO89BA>=`AS&k+7OvG+Rs$y$@95&z) z)Jy~0Jl!7HE-guP(BI*7UrULhqH~mYx{{AK^Al;K|8*aV{|e+ah<0(nMH{w|X>8cU z5>lczdCVtIZ3ZHy+|*;73FfM8>Xn$Xs>HBe1Oo`lO#ecM)Cu2eE8GLxhYOy*npczo zO`)rRNB~Se`RZH(G znPOZi%(ksKV0x}1UXf8n?R$*3`7~hFw&PI4&qXjdY?b0aI#FK9KNe=)`HcarVaTKy zcehBEMU+Jre#NM3Va8S@8yvzAZ;2U;A4-6FA*vE@tjSS`d^7FS!E39yAex)xmT120 zg{OoahD@ip*_b-5R;w<8GOK189uKS$1N(Qu+q4ag?%_DgX=Q5oT2|!=GnQsWh0b~! z&;|h=`dqt!Lxd%%>;f^C6_k28$vXb){U@oAc=E#rDWt$w_(Z98lDbNTD@<|McthEt z&7finGk#+SIN#);MJzB5EK}$PItl+_+}nfGy7~_|!{{upByLO(LBwkJMj@n0zAnMgJf z8k5X00#zrLC=*5{xeUC7sm18$N4;4c#1tbQr)EPPCR}<5c*9%8vi-B8Gm7WO~i}2REB?f*c%C|K>iyxGak>bMI++=m73*NZ-c6@!G`#NI`git9Z zUy2n>r(ii2Q%Hg?0;ZpTyet6PJCmns$O*dt&vgj}{lQ(P-rQm}XTN9=Dkv@q} z5pDViTupwPmk%JPd-#tyO@=(chvz=ey5)m;qqfLbbW#G)%16`P(6h6&IC%q^XcrIS zMLdZ&uEb{l6~b{%u7d7!*_zuXnG+)WNBrp-15fL?3@(S7v((awQXEgP+`cg@L9^B4 zR+vJioUCP0J$x#*AHVK?y2q=TMLHfIGS(rN5*Z-c#0N;jZK=(&dfno?%)$miqEWPp zX3;Jl#7k`A(SexG#kIVg@+qRDG7P5w%%JZ$_9G=-#EDK`g9I{QDX_G1A`1IE=G$N9 z)&S*DfZT^)C@W)@ChC%(hrj#`?`_?L|9Ay|RdB>9r=0S-LC6dXDS(%50%3M5y&lI^ zExc$DEuu-ZiN?(MAV8{Ddj#B?t*!W0dA5lB^dsBV$oSLQ?=`p%1`)&PU9Kv;(Iczq z$pI3%0R=)hJ1tqVa;C=%x zycD4E*EGt{-+AYq`m&Ca7*#o=T_>73)h&c3U`CcwiZlVUrs9aFo_gwI&p-eCF@PY* zld`)|l*$e%Iau@zs?p=j(&g4ioV6g3$3~Z4&^e`^o}R*n4I2sqWo$ZEC7I=e1{Kkd zEbNeVMLhzon6Oqz<0|k$>3^dEei@5<8|RhO==BUeyOTyh0#Zg&DhoaqbLu7y76Asp^B!J!xFn1HKiXzHonFSKvuYq95C~}n< zsg!j$`ka>Jui};z2!Zty5qXI1H@FJ_##rD^_*|1AP$Nk-(jq1^*IRmbG?X&Z0uzq{ zvOpl70``a-e3jogQP5xi^ZMWT~b46V#U!R%&G?ca>wMWYzWiv_;SQ`I9JiPl@qGaIzp%9b$!yt!KmF-X z^-f_njfb}aycM}m*#hmjUnaPAWHN^*4gAJc0<+3%E$zaE3%4W2yel(gyD|asR=VgN znS^*V)^aoAxsw(xTC@qXaufAHLy3!QxMB7{1QpV%BT=H>cY~_+ecFVP5U--foreYPhc=+Lmr~mrb zzn+2Bo&>;+=jOR@GVKku%gxm>jVf7=A>tizi{~{rHdzld7Q2+n{07*qoM6N<$ Ef~DIy`Tzg` literal 0 HcmV?d00001 diff --git a/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/close.imageset/close.png b/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/close.imageset/close.png new file mode 100644 index 0000000000000000000000000000000000000000..73ee3d8f9ab05a5b8a804af3746fc2553d0ab48a GIT binary patch literal 10029 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 literal 0 HcmV?d00001 diff --git a/DrawerKitDemo/DrawerKitDemo/CubicBezierView.swift b/DrawerKitDemo/DrawerKitDemo/CubicBezierView.swift deleted file mode 100644 index a5fdee4..0000000 --- a/DrawerKitDemo/DrawerKitDemo/CubicBezierView.swift +++ /dev/null @@ -1,299 +0,0 @@ -import UIKit - -protocol CubicBezierViewDelegate: class { - func cubicBezierView(_ view: CubicBezierView, - controlPoint1: CGPoint, - controlPoint2: CGPoint) -} - -@IBDesignable -class CubicBezierView: UIControl, HandleViewDelegate { - weak var delegate: CubicBezierViewDelegate? - private var renderer = Renderer() - private var handleAtZeroZero: HandleView! - private var handleAtOneOne: HandleView! - private var actualW: CGFloat = 0 - private var actualH: CGFloat = 0 - private var runningUnderIB = false - private let fakeSize: CGFloat = 250 - private let dimmingView = UIView() - - @IBInspectable var fractionalRadius: CGFloat = 0.6 { - didSet { setup(); setNeedsDisplay() } - } - - @IBInspectable var curveLineWidth: CGFloat = 5 { - didSet { renderer.curveLineWidth = curveLineWidth; setNeedsDisplay() } - } - - @IBInspectable var curveLineColor: UIColor = .black { - didSet { renderer.curveLineColor = curveLineColor; setNeedsDisplay() } - } - - @IBInspectable var handleSize: CGFloat = 25 { - didSet { setup(); renderer.handleSize = handleSize; setNeedsDisplay() } - } - - @IBInspectable var handleLineWidth: CGFloat = 2 { - didSet { renderer.handleLineWidth = handleLineWidth; setNeedsDisplay() } - } - - @IBInspectable var handleLineColor: UIColor = .white { - didSet { renderer.handleLineColor = handleLineColor; setNeedsDisplay() } - } - - @IBInspectable var handleBorderWidth: CGFloat = 3 { - didSet { renderer.handleBorderWidth = handleBorderWidth; setNeedsDisplay() } - } - - @IBInspectable var handleBorderColor: UIColor = .white { - didSet { renderer.handleBorderColor = handleBorderColor; setNeedsDisplay() } - } - - @IBInspectable var handleInteriorColor: UIColor = .lightGray { - didSet { renderer.handleInteriorColor = handleInteriorColor; setNeedsDisplay() } - } - - override init(frame: CGRect) { - super.init(frame: frame) - computeActualSize() - } - - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - } - - override func draw(_ rect: CGRect) { - renderer.renderCurve() - renderer.renderControls() - } - - override var isEnabled: Bool { - didSet { - if isEnabled { - dimmingView.removeFromSuperview() - isUserInteractionEnabled = true - } else { - addSubview(dimmingView) - dimmingView.frame = bounds - dimmingView.backgroundColor = UIColor.black.withAlphaComponent(0.15) - isUserInteractionEnabled = false - } - } - } - - fileprivate func handleViewDidMove(_ view: HandleView) { - switch view { - case handleAtZeroZero: - renderer.frame1 = view.frame - case handleAtOneOne: - renderer.frame2 = view.frame - default: - return - } - - delegate?.cubicBezierView(self, - controlPoint1: handleAtZeroZero.center / actualW, - controlPoint2: handleAtOneOne.center / actualW) - - setNeedsDisplay() - } - - override var frame: CGRect { - didSet { computeActualSize() } - } - - private func setup() { - var pointAtZeroZero = CGPoint( - x: (bounds.size.width - actualW) / 2, - y: (bounds.size.height - actualH) / 2 + actualH - ) - - // This is needed because IBDesignable doesn't wait for layout. - if actualW == 0 || actualH == 0 { - runningUnderIB = true - actualW = fakeSize - actualH = fakeSize - pointAtZeroZero = CGPoint(x: 0, y: actualH) - } - - if handleAtZeroZero == nil { - handleAtZeroZero = HandleView(anchor: pointAtZeroZero, - inverted: false, - delegate: self) - addSubview(handleAtZeroZero) - } - handleAtZeroZero.fractionalRadius = fractionalRadius - handleAtZeroZero.frame.size.width = handleSize - handleAtZeroZero.frame.size.height = handleSize - handleAtZeroZero.center.x = pointAtZeroZero.x + fractionalRadius * actualW - handleAtZeroZero.center.y = pointAtZeroZero.y - - var pointAtOneOne = pointAtZeroZero - pointAtOneOne.x += actualW - pointAtOneOne.y -= actualH - if handleAtOneOne == nil { - handleAtOneOne = HandleView(anchor: pointAtOneOne, - inverted: true, - delegate: self) - addSubview(handleAtOneOne) - } - handleAtOneOne.fractionalRadius = fractionalRadius - handleAtOneOne.frame.size.width = handleSize - handleAtOneOne.frame.size.height = handleSize - handleAtOneOne.center.x = pointAtOneOne.x - fractionalRadius * actualW - handleAtOneOne.center.y = pointAtOneOne.y - - if runningUnderIB { - let offsetY1: CGFloat = (-30.0/250.0) * fakeSize - handleAtZeroZero.setCenter(for: offsetY1) - let offsetY2: CGFloat = (30.0/250.0) * fakeSize - handleAtOneOne.setCenter(for: offsetY2) - } - - updateRenderer() - } - - private func updateRenderer() { - renderer.anchor1 = handleAtZeroZero.anchor - renderer.frame1 = handleAtZeroZero.frame - renderer.anchor2 = handleAtOneOne.anchor - renderer.frame2 = handleAtOneOne.frame - } - - private func computeActualSize() { - actualW = min(frame.size.width, frame.size.height) - actualH = actualW - setup() - setNeedsDisplay() - } -} - -private protocol HandleViewDelegate: class { - func handleViewDidMove(_ view: HandleView) -} - -private class HandleView: UIView { - let anchor: CGPoint - let inverted: Bool - var fractionalRadius: CGFloat? - weak var delegate: HandleViewDelegate? - - init(anchor: CGPoint, inverted: Bool, delegate: HandleViewDelegate?) { - self.anchor = anchor - self.inverted = inverted - self.delegate = delegate - super.init(frame: CGRect.zero) - setup() - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override var center: CGPoint { - didSet { delegate?.handleViewDidMove(self) } - } - - func setup() { - backgroundColor = .clear - let gr = UIPanGestureRecognizer(target: self, action: #selector(handleDrag(gr:))) - addGestureRecognizer(gr) - } - - @objc - func handleDrag(gr: UIPanGestureRecognizer) { - let offset = gr.translation(in: gr.view?.superview) - gr.setTranslation(.zero, in: gr.view?.superview) - switch gr.state { - case .changed: - setCenter(for: offset.y) - default: - break - } - } - - func setCenter(for offsetY: CGFloat) { - guard let superview = superview else { return } - guard let fractionalRadius = fractionalRadius else { return } - let s: CGFloat = (inverted ? -1 : 1) - var pos = center - pos.y += offsetY - let dy = s * (pos.y - anchor.y) - guard dy <= 0 else { return } - let svW = superview.bounds.size.width - let svH = superview.bounds.size.height - let radius = fractionalRadius * min(svW, svH) - let rsq = radius * radius - let dxsq = rsq - dy * dy - guard dxsq >= 0 else { return } - let dx = s * sqrt(dxsq) - pos.x = (anchor.x + dx) - center = pos - } -} - -private struct Renderer { - var curveLineWidth: CGFloat = 5 - var curveLineColor: UIColor = .black - - var handleSize: CGFloat = 25 - var handleInteriorColor: UIColor = .lightGray - - var handleLineWidth: CGFloat = 2 - var handleLineColor: UIColor = .white - - var handleBorderWidth: CGFloat = 3 - var handleBorderColor: UIColor = .white - - var anchor1: CGPoint = .zero - var frame1: CGRect = .zero - var center1: CGPoint { return center(frame1) } - - var anchor2: CGPoint = .zero - var frame2: CGRect = .zero - var center2: CGPoint { return center(frame2) } - - func renderControls() { - renderControl(anchor: anchor1, frame: frame1) - renderControl(anchor: anchor2, frame: frame2) - } - - func renderCurve() { - curveLineColor.setStroke() - let path = UIBezierPath() - path.move(to: anchor1) - path.addCurve(to: anchor2, controlPoint1: center1, controlPoint2: center2) - path.lineWidth = curveLineWidth - path.stroke() - } - - func renderControl(anchor: CGPoint, frame: CGRect) { - handleLineColor.setStroke() - let path = UIBezierPath() - path.move(to: anchor) - path.addLine(to: center(frame)) - path.lineWidth = handleLineWidth - path.stroke() - - handleBorderColor.setFill() - UIBezierPath(ovalIn: frame).fill() - handleInteriorColor.setFill() - let insetRect = frame.insetBy(dx: handleBorderWidth, dy: handleBorderWidth) - UIBezierPath(ovalIn: insetRect).fill() - } - - func center(_ frame: CGRect) -> CGPoint { - var c = frame.origin - c.x += frame.size.width / 2 - c.y += frame.size.height / 2 - return c - } -} - -private extension CGPoint { - static func /(lhs: CGPoint, rhs: CGFloat) -> CGPoint { - return CGPoint(x: lhs.x / rhs, y: lhs.y / rhs) - } -} - diff --git a/DrawerKitDemo/DrawerKitDemo/PresentedView.swift b/DrawerKitDemo/DrawerKitDemo/PresentedView.swift index 69da8ca..b80e169 100644 --- a/DrawerKitDemo/DrawerKitDemo/PresentedView.swift +++ b/DrawerKitDemo/DrawerKitDemo/PresentedView.swift @@ -2,4 +2,5 @@ import UIKit class PresentedView: UIView { @IBOutlet weak var dividerView: UIView! + @IBOutlet weak var imageView: UIImageView! } diff --git a/DrawerKitDemo/DrawerKitDemo/PresentedViewController.swift b/DrawerKitDemo/DrawerKitDemo/PresentedViewController.swift index 277410a..3e3fa45 100644 --- a/DrawerKitDemo/DrawerKitDemo/PresentedViewController.swift +++ b/DrawerKitDemo/DrawerKitDemo/PresentedViewController.swift @@ -1,28 +1,21 @@ import UIKit import DrawerKit -// Search for the string 'THIS IS THE IMPORTANT PART' in both view controllers -// to see how to show the drawer. There may be more than one important part in -// each view controller. - class PresentedViewController: UIViewController { - var hasFixedHeight = false - - override func loadView() { - super.loadView() - view.heightAnchor.constraint(equalToConstant: 290).isActive = hasFixedHeight - } - @IBAction func dismissButtonTapped() { dismiss(animated: true) } } -// ======== THIS IS THE IMPORTANT PART ======== // extension PresentedViewController: DrawerPresentable { var heightOfPartiallyExpandedDrawer: CGFloat { guard let view = self.view as? PresentedView else { return 0 } return view.dividerView.frame.origin.y } } -// ============================================ // + +extension PresentedViewController: UIScrollViewDelegate { + func viewForZooming(in scrollView: UIScrollView) -> UIView? { + return (view as? PresentedView)?.imageView + } +} diff --git a/DrawerKitDemo/DrawerKitDemo/PresenterView.swift b/DrawerKitDemo/DrawerKitDemo/PresenterView.swift deleted file mode 100644 index c524419..0000000 --- a/DrawerKitDemo/DrawerKitDemo/PresenterView.swift +++ /dev/null @@ -1,3 +0,0 @@ -import UIKit - -class PresenterView: UIView {} diff --git a/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift b/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift index 3beb703..feb3b26 100644 --- a/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift +++ b/DrawerKitDemo/DrawerKitDemo/PresenterViewController.swift @@ -1,154 +1,39 @@ import UIKit import DrawerKit -// TODO: -// - add the remainder of configuration controls and hook them up - -// Search for the string 'THIS IS THE IMPORTANT PART' in both view controllers -// to see how to show the drawer. There may be more than one important part in -// each view controller. - -// ======== THIS IS THE IMPORTANT PART ======== // class PresenterViewController: UIViewController, DrawerPresenting { /* strong */ var drawerDisplayController: DrawerDisplayController? - // ============================================ // - - private var durationInSeconds: CGFloat = 0.8 - private var hasFixedHeight = false - private var supportsPartialExpansion = true - private var dismissesInStages = true - private var isDrawerDraggable = true - private var isDismissableByOutsideDrawerTaps = true - private var numberOfTapsForOutsideDrawerDismissal: Int = 1 - private var flickSpeedThreshold: CGFloat = 3 - private var upperMarkGap: CGFloat = 40 - private var lowerMarkGap: CGFloat = 40 - private var maximumCornerRadius: CGFloat = 30 - - @IBOutlet weak var hasFixedHeightSwitch: UISwitch! - @IBOutlet weak var supportsPartialExpansionSwitch: UISwitch! - @IBOutlet weak var dismissesInStagesSwitch: UISwitch! - @IBOutlet weak var drawerDraggableSwitch: UISwitch! - @IBOutlet weak var dismissableByOutsideTapButton: UIButton! - @IBOutlet weak var durationSliderView: SliderView! - @IBOutlet weak var flickSpeedThresholdSliderView: SliderView! - @IBOutlet weak var upperMarkGapSliderView: SliderView! - @IBOutlet weak var lowerMarkGapSliderView: SliderView! - @IBOutlet weak var maximumCornerRadiusSliderView: SliderView! - override func viewDidLoad() { - super.viewDidLoad() - setup() + @IBAction func presentButtonTapped() { + doModalPresentation() } } -extension PresenterViewController { - private func doModalPresentation() { - let sb = UIStoryboard(name: "PresentedVC", bundle: nil) - guard let vc = sb.instantiateViewController(withIdentifier: "presented") +private extension PresenterViewController { + func doModalPresentation() { + guard let vc = storyboard?.instantiateViewController(withIdentifier: "presented") as? PresentedViewController else { return } - vc.hasFixedHeight = hasFixedHeight - // ======== THIS IS THE IMPORTANT PART ======== // // you can provide the configuration values in the initialiser... var configuration = DrawerConfiguration(/* ..., ..., ..., */) // ... or after initialisation - configuration.durationInSeconds = 0.8 // TimeInterval(durationSliderView.value) + configuration.durationInSeconds = 0.8 configuration.timingCurveProvider = UISpringTimingParameters(dampingRatio: 0.8) - configuration.supportsPartialExpansion = supportsPartialExpansion - configuration.dismissesInStages = dismissesInStages - configuration.isDrawerDraggable = isDrawerDraggable - configuration.isDismissableByOutsideDrawerTaps = isDismissableByOutsideDrawerTaps - configuration.numberOfTapsForOutsideDrawerDismissal = numberOfTapsForOutsideDrawerDismissal - configuration.flickSpeedThreshold = 3 // flickSpeedThresholdSliderView.value.cgFloat - configuration.upperMarkGap = 0.9 // upperMarkGapSliderView.value.cgFloat + configuration.supportsPartialExpansion = true + configuration.dismissesInStages = true + configuration.isDrawerDraggable = true + configuration.isDismissableByOutsideDrawerTaps = true + configuration.numberOfTapsForOutsideDrawerDismissal = 1 + configuration.flickSpeedThreshold = 3 configuration.upperMarkGap = 30 - configuration.lowerMarkGap = 0.2 // lowerMarkGapSliderView.value.cgFloat configuration.lowerMarkGap = 30 - configuration.maximumCornerRadius = 20 // maximumCornerRadiusSliderView.value.cgFloat + configuration.maximumCornerRadius = 20 drawerDisplayController = DrawerDisplayController(presentingViewController: self, presentedViewController: vc, - configuration: configuration, - inDebugMode: true) - // ============================================ // + configuration: configuration) present(vc, animated: true) } } - -extension PresenterViewController { - @IBAction func presentButtonTapped() { - doModalPresentation() - } - - @IBAction func switchToggled(sender: UISwitch) { - handleSwitchToggled(sender) - } - - @IBAction func numberOfTapsButtonTapped(_ sender: UIButton) { - handleNumberOfTapsButtonTapped(sender) - } -} - -private extension PresenterViewController { - func setup() { -// durationSliderView.configureWith( -// title: "Duration in secs", minValue: 0.3, maxValue: 5, -// initialValue: 0.8, defaultValue: 0.8) -// flickSpeedThresholdSliderView.configureWith( -// title: "Speed threshold", minValue: 1, maxValue: 5, -// initialValue: 3, defaultValue: 3) -// upperMarkGapSliderView.configureWith( -// title: "Upper mark gap", minValue: 0, maxValue: 100, -// initialValue: 0.8, defaultValue: 0.8) -// lowerMarkGapSliderView.configureWith( -// title: "Lower mark gap", minValue: 0, maxValue: 100, -// initialValue: 0.5, defaultValue: 0.5) -// maximumCornerRadiusSliderView.configureWith( -// title: "Max corner radius", minValue: 0, maxValue: 30, -// initialValue: 15, defaultValue: 15) -// hasFixedHeightSwitch.isOn = hasFixedHeight -// supportsPartialExpansionSwitch.isOn = supportsPartialExpansion -// dismissesInStagesSwitch.isEnabled = supportsPartialExpansion -// dismissesInStagesSwitch.isOn = dismissesInStages -// drawerDraggableSwitch.isOn = isDrawerDraggable -// dismissableByOutsideTapButton.setTitle("\(numberOfTapsForOutsideDrawerDismissal)", for: .normal) - } - - func handleSwitchToggled(_ toggler: UISwitch) { - switch toggler { - case hasFixedHeightSwitch: - hasFixedHeight = toggler.isOn - case supportsPartialExpansionSwitch: - supportsPartialExpansion = toggler.isOn - dismissesInStagesSwitch.isEnabled = toggler.isOn - case dismissesInStagesSwitch: - dismissesInStages = toggler.isOn - case drawerDraggableSwitch: - isDrawerDraggable = toggler.isOn - default: - return - } - } - - func handleNumberOfTapsButtonTapped(_ button: UIButton) { - let curValue = Int(button.titleLabel?.text ?? "0") ?? 0 - let newValue = (curValue + 1) % 4 - button.setTitle("\(newValue)", for: .normal) - switch button { - case dismissableByOutsideTapButton: - isDismissableByOutsideDrawerTaps = (newValue > 0) - numberOfTapsForOutsideDrawerDismissal = newValue - default: - return - } - } -} - -extension Double { - var cgFloat: CGFloat { - return CGFloat(self) - } -} diff --git a/DrawerKitDemo/DrawerKitDemo/SliderView.swift b/DrawerKitDemo/DrawerKitDemo/SliderView.swift deleted file mode 100644 index f106c00..0000000 --- a/DrawerKitDemo/DrawerKitDemo/SliderView.swift +++ /dev/null @@ -1,316 +0,0 @@ -import UIKit - -@IBDesignable -class SliderView: UIControl { - let label = UILabel() - let textField = UITextField() - let minimumValueButton: UIButton = UIButton(type: .custom) - let maximumValueButton: UIButton = UIButton(type: .custom) - let slider = UISlider() - - @IBInspectable var title: String? { - get { return label.text } - set { label.text = newValue } - } - - @IBInspectable var textColor: UIColor { - get { return label.textColor } - set { - label.textColor = newValue - textField.textColor = newValue - minimumValueButton.setTitleColor(newValue, for: .normal) - maximumValueButton.setTitleColor(newValue, for: .normal) - } - } - - @IBInspectable var minimumValue: Double { - get { return Double(slider.minimumValue) } - set { - slider.minimumValue = Float(newValue) - minimumValueButton.setTitle(format(value: newValue), for: .normal) - } - } - - @IBInspectable var maximumValue: Double { - get { return Double(slider.maximumValue) } - set { - slider.maximumValue = Float(newValue) - maximumValueButton.setTitle(format(value: newValue), for: .normal) - } - } - - @IBInspectable var value: Double { - get { return Double(_value) } - set { - let currentValue = Double(_value) - guard newValue != currentValue else { return } - set(value: newValue) - } - } - - @IBInspectable var maximumFractionDigits: Int { - get { return formatter.maximumFractionDigits } - set { - formatter.maximumFractionDigits = max(0, newValue) - minimumValueButton.setTitle(format(value: sanitize(value: minimumValue)), for: .normal) - maximumValueButton.setTitle(format(value: sanitize(value: maximumValue)), for: .normal) - textField.text = format(value: sanitize(value: value)) - } - } - - @IBInspectable var minimumTrackTintColor: UIColor? { - get { return slider.minimumTrackTintColor } - set { slider.minimumTrackTintColor = newValue } - } - - @IBInspectable var maximumTrackTintColor: UIColor? { - get { return slider.maximumTrackTintColor } - set { slider.maximumTrackTintColor = newValue } - } - - @IBInspectable var thumbTintColor: UIColor? { - get { return slider.thumbTintColor } - set { slider.thumbTintColor = newValue } - } - - init(title: String? = nil, - minimumValue: Double = 0, - maximumValue: Double = 1, - initialValue: Double = 0.5) { - super.init(frame: .zero) - let minValue = min(minimumValue, maximumValue) - let maxValue = max(minimumValue, maximumValue) - self.minimumValue = minValue - self.maximumValue = maxValue - self.initialValue = min(max(minValue, initialValue), maxValue) - self.title = title - setup() - } - - override init(frame: CGRect) { - super.init(frame: frame) - setup() - } - - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - setup() - } - - override var intrinsicContentSize: CGSize { - return containerViewIntrinsicContentSize - } - - override var isEnabled: Bool { - didSet { - textField.isEnabled = isEnabled - minimumValueButton.isEnabled = isEnabled - maximumValueButton.isEnabled = isEnabled - slider.isEnabled = isEnabled - } - } - - override func addTarget(_ target: Any?, - action: Selector, - for controlEvents: UIControlEvents) { - slider.addTarget(target, action: action, for: controlEvents) - } - - override func removeTarget(_ target: Any?, - action: Selector?, - for controlEvents: UIControlEvents) { - slider.removeTarget(target, action: action, for: controlEvents) - } - - private var initialValue: Double = 0.5 - private var _value: Double = 0.5 - - private let containerView = UIStackView() - private let textContainer = UIStackView() - private let upperView = UIStackView() - - private let textFont = UIFont.systemFont(ofSize: 15) - private let minMaxButtonW: CGFloat = 35 - private let minMaxButtonH: CGFloat = 35 - private let formatter = NumberFormatter() -} - -private extension SliderView { - enum ButtonTag: Int { - case min = 0 - case max - } - - func setup() { - setupFormatter() - setupLabel() - setupTextField() - setupTextContainer() - setupButton(minimumValueButton, value: minimumValue, tag: .min) - minimumValueButton.contentHorizontalAlignment = .left - setupButton(maximumValueButton, value: maximumValue, tag: .max) - maximumValueButton.contentHorizontalAlignment = .right - setupUpperView() - setupSlider() - setupContainerView() - setupSelf() - } - - func setupFormatter() { - formatter.allowsFloats = true - formatter.minimumIntegerDigits = 1 - formatter.minimumFractionDigits = 1 - formatter.maximumFractionDigits = 2 - } - - func setupLabel() { - label.font = textFont - label.text = title - label.textAlignment = .right - } - - func setupTextField() { - textField.text = format(value: initialValue) - textField.font = textFont - textField.textAlignment = .left - textField.delegate = self - } - - func setupTextContainer() { - textContainer.axis = .horizontal - textContainer.alignment = .firstBaseline - textContainer.distribution = .fill - textContainer.spacing = 8 - upperView.addArrangedSubview(minimumValueButton) - textContainer.addArrangedSubview(label) - textContainer.addArrangedSubview(textField) - textContainer.addArrangedSubview(UIView()) - } - - var textContainerIntrinsicContentSize: CGSize { - var size = CGSize.zero - let labelS = label.intrinsicContentSize - let minPadW = textContainer.spacing - let fieldS = textField.intrinsicContentSize - size.width = minMaxButtonW + 3 * minPadW + labelS.width + fieldS.width - size.height = max(minMaxButtonH, labelS.height, fieldS.height) - return size - } - - func setupButton(_ button: UIButton, value: Double, tag: ButtonTag) { - button.addTarget(self, action: #selector(buttonTapped(btn:)), for: .touchUpInside) - button.translatesAutoresizingMaskIntoConstraints = false - button.widthAnchor.constraint(equalToConstant: minMaxButtonW).isActive = true - button.heightAnchor.constraint(equalToConstant: minMaxButtonH).isActive = true - button.titleLabel?.font = label.font - button.setTitleColor(label.textColor, for: .normal) - button.setTitle(format(value: sanitize(value: value)), for: .normal) - button.tag = tag.rawValue - } - - func setupUpperView() { - upperView.axis = .horizontal - upperView.alignment = .firstBaseline - upperView.distribution = .fill - upperView.spacing = 8 - upperView.addArrangedSubview(textContainer) - upperView.addArrangedSubview(maximumValueButton) - } - - var upperViewIntrinsicContentSize: CGSize { - var size = CGSize.zero - let textContainerS = textContainerIntrinsicContentSize - size.width = textContainerS.width + upperView.spacing + minMaxButtonW - size.height = max(textContainerS.height, minMaxButtonH) - return size - } - - func setupSlider() { - slider.addTarget(self, action: #selector(sliderValueChanged), for: .valueChanged) - slider.minimumValue = Float(minimumValue) - slider.maximumValue = Float(maximumValue) - slider.value = Float(initialValue) - } - - func setupContainerView() { - containerView.axis = .vertical - containerView.alignment = .fill - containerView.distribution = .fill - containerView.spacing = 0 - containerView.addArrangedSubview(upperView) - containerView.addArrangedSubview(slider) - } - - var containerViewIntrinsicContentSize: CGSize { - var size = CGSize.zero - let upperViewS = upperViewIntrinsicContentSize - let sliderS = slider.intrinsicContentSize - size.width = max(upperViewS.width, sliderS.width) - size.height = upperViewS.height + containerView.spacing + sliderS.height - return size - } - - func setupSelf() { - addSubview(containerView) - translatesAutoresizingMaskIntoConstraints = false - containerView.translatesAutoresizingMaskIntoConstraints = false - containerView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true - containerView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true - containerView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true - } -} - -extension SliderView: UITextFieldDelegate { - func textFieldShouldReturn(_ textField: UITextField) -> Bool { - textField.resignFirstResponder() - return true - } - - func textFieldDidEndEditing(_ textField: UITextField) { - guard let value = Double(textField.text ?? "\(initialValue)") else { - self.value = initialValue - return - } - self.value = value - } -} - -private extension SliderView { - @objc func buttonTapped(btn: UIButton) { - guard let btnTag = ButtonTag(rawValue: btn.tag) else { return } - switch btnTag { - case .min: - self.value = Double(slider.minimumValue) - case .max: - self.value = Double(slider.maximumValue) - } - } - - @objc func sliderValueChanged() { - guard value != Double(slider.value) else { return } - self.value = Double(slider.value) - } -} - -private extension SliderView { - func set(value: Double) { - let sanitisedValue = sanitize(value: value) - textField.text = format(value: sanitisedValue) - slider.value = Float(sanitisedValue) - _value = Double(sanitisedValue) - } - - func sanitize(value: Double) -> Double { - let minimumValue = Double(slider.minimumValue) - let maximumValue = Double(slider.maximumValue) - let value = min(maximumValue, max(value, minimumValue)) - var k = maximumFractionDigits - var factor: Double = 1; while k > 0 { factor *= 10; k -= 1 } - return Double(trunc(factor * value)) / factor - } - - func format(value: Double) -> String? { - let nsValue = NSNumber(value: value) - return formatter.string(from: nsValue) - } -} diff --git a/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/Main.storyboard b/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/Main.storyboard index 479f9f9..2a0d075 100644 --- a/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/Main.storyboard +++ b/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/Main.storyboard @@ -1,73 +1,159 @@ - + + - - + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + - + - - + + - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + diff --git a/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresentedVC.storyboard b/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresentedVC.storyboard deleted file mode 100644 index 71c7ad3..0000000 --- a/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresentedVC.storyboard +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresenterVC.storyboard b/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresenterVC.storyboard deleted file mode 100644 index 2e3f272..0000000 --- a/DrawerKitDemo/DrawerKitDemo/Storyboards/Base.lproj/PresenterVC.storyboard +++ /dev/null @@ -1,780 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DrawerKitDemo/DrawerKitDemo/SwitchView.swift b/DrawerKitDemo/DrawerKitDemo/SwitchView.swift deleted file mode 100644 index bbb11e1..0000000 --- a/DrawerKitDemo/DrawerKitDemo/SwitchView.swift +++ /dev/null @@ -1,168 +0,0 @@ -import UIKit - -@IBDesignable -class SwitchView: UIControl { - let label = UILabel() - let toggler = UISwitch() - - @IBInspectable var title: String? { - get { return label.text } - set { label.text = newValue } - } - - @IBInspectable var textColor: UIColor { - get { return label.textColor } - set { label.textColor = newValue } - } - - @IBInspectable var isOn: Bool { - get { return toggler.isOn } - set { toggler.isOn = newValue } - } - - @IBInspectable var onTint: UIColor? { - get { return toggler.onTintColor } - set { toggler.onTintColor = newValue } - } - - @IBInspectable var thumbTint: UIColor? { - get { return toggler.thumbTintColor } - set { toggler.thumbTintColor = newValue } - } - - @IBInspectable var indented: Bool { - get { return !indentationView.isHidden } - set { indentationView.isHidden = !newValue } - } - - @IBInspectable var indentation: CGFloat { - get { return indentationConstraint?.constant ?? 0 } - set { - indentationConstraint?.constant = newValue - layoutIfNeeded() - } - } - - init(title: String? = nil, - initiallyOn: Bool = true, - indented: Bool = false, - indentation: CGFloat = 20) { - super.init(frame: .zero) - self.title = title - self.isOn = initiallyOn - self.indented = indented - self.indentation = indentation - setup() - } - - override init(frame: CGRect) { - super.init(frame: frame) - setup() - } - - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - setup() - } - - override var intrinsicContentSize: CGSize { - var size = CGSize.zero - let labelS = label.intrinsicContentSize - let togglerS = toggler.intrinsicContentSize - let indentW = (indented ? indentation : 0) - size.width = indentW + labelS.width + minPadViewW + togglerS.width - size.height = max(labelS.height, togglerS.height) - return size - } - - override var isEnabled: Bool { - didSet { toggler.isEnabled = isEnabled } - } - - override func addTarget(_ target: Any?, - action: Selector, - for controlEvents: UIControlEvents) { - toggler.addTarget(target, action: action, for: controlEvents) - } - - override func removeTarget(_ target: Any?, - action: Selector?, - for controlEvents: UIControlEvents) { - toggler.removeTarget(target, action: action, for: controlEvents) - } - - private let containerView = UIStackView() - private let padViewL = UIView() - private let padViewR = UIView() - private let minPadViewW: CGFloat = 8 - private let indentationView = UIView() - private var indentationConstraint: NSLayoutConstraint! - private let textFont = UIFont.systemFont(ofSize: 15) -} - -private extension SwitchView { - func setup() { - toggler.isOn = isOn - setupLabel() - setupContainerView() - setupSelf() - setupConstraints() - } - - func setupLabel() { - label.font = textFont - label.textAlignment = .left - label.text = title - } - - func setupContainerView() { - containerView.axis = .horizontal - containerView.alignment = .center - containerView.distribution = .fill - containerView.spacing = 0 - containerView.addArrangedSubview(indentationView) - containerView.addArrangedSubview(label) - containerView.addArrangedSubview(padViewL) - containerView.addArrangedSubview(toggler) - containerView.addArrangedSubview(padViewR) - } - - func setupSelf() { - backgroundColor = .clear - addSubview(containerView) - } - - func setupConstraints() { - setupIndentationViewConstraints() - setupPadViewLConstraints() - setupPadViewRConstraints() - setupContainerViewConstraints() - } - - func setupIndentationViewConstraints() { - indentationView.translatesAutoresizingMaskIntoConstraints = false - indentationView.heightAnchor.constraint(equalToConstant: 5).isActive = true - indentationConstraint = indentationView.widthAnchor.constraint(equalToConstant: indentation) - indentationConstraint.isActive = true - indentationView.isHidden = !indented - } - - func setupPadViewLConstraints() { - padViewL.translatesAutoresizingMaskIntoConstraints = false - padViewL.widthAnchor.constraint(greaterThanOrEqualToConstant: minPadViewW).isActive = true - padViewL.heightAnchor.constraint(equalToConstant: 5).isActive = true - } - - func setupPadViewRConstraints() { - padViewR.translatesAutoresizingMaskIntoConstraints = false - padViewR.widthAnchor.constraint(equalToConstant: 2).isActive = true - } - - func setupContainerViewConstraints() { - translatesAutoresizingMaskIntoConstraints = false - containerView.translatesAutoresizingMaskIntoConstraints = false - containerView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true - containerView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true - containerView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true - } -} From f52a637628ffc9a2a7cfdd5fa4839c1022d8ac3b Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Sun, 15 Oct 2017 17:11:48 +0100 Subject: [PATCH 57/85] Added based documentation to the library. --- .../Public API/DrawerConfiguration.swift | 2 + ...rawerDisplayController+Configuration.swift | 44 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift b/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift index ddb818f..9a53b46 100644 --- a/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift +++ b/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift @@ -1,6 +1,8 @@ import UIKit /// All the configurable parameters in one place. +/// See `DrawerDisplayController+Configuration.swift` for documentation on +/// what the various parameters are used for. public struct DrawerConfiguration: Equatable { public var durationInSeconds: TimeInterval public var timingCurveProvider: UITimingCurveProvider diff --git a/DrawerKit/DrawerKit/Public API/DrawerDisplayController+Configuration.swift b/DrawerKit/DrawerKit/Public API/DrawerDisplayController+Configuration.swift index 046019f..b680bf1 100755 --- a/DrawerKit/DrawerKit/Public API/DrawerDisplayController+Configuration.swift +++ b/DrawerKit/DrawerKit/Public API/DrawerDisplayController+Configuration.swift @@ -3,46 +3,90 @@ import UIKit /// A collection of convenience getter functions to access the drawer /// configuration parameters directly from the drawer display controller. extension DrawerDisplayController { + /// How long the animations that move the drawer up and down last. + /// The default value is 0.8 seconds. public var durationInSeconds: TimeInterval { return configuration.durationInSeconds } + /// The type of timing curve to use for the animations. The full set + /// of cubic Bezier curves and spring-based curves is supported. Note + /// that selecting a spring-based timing curve causes the `durationInSeconds` + /// parameter to be ignored, because the duration is computed based on the + /// specifics of the spring-based curve. The default is `UISpringTimingParameters()`, + /// which is the system's global spring-based timing curve. public var timingCurveProvider: UITimingCurveProvider { return configuration.timingCurveProvider } + /// When `true`, the drawer is presented first in its partially expanded state. + /// When `false`, the presentation is always to full screen and there is no + /// partially expanded state. The default value is `true`. public var supportsPartialExpansion: Bool { return configuration.supportsPartialExpansion } + /// When `true`, dismissing the drawer from its fully expanded state can result + /// in the drawer stopping at its partially expanded state. When `false`, the + /// dismissal is always straight to the collapsed state. Note that + /// `supportsPartialExpansion` being `false` implies `dismissesInStages` being + /// `false` as well but you can have `supportsPartialExpansion == true` and + /// `dismissesInStages == false`, which would result in presentations to the + /// partially expanded state but all dismissals would be straight to the collapsed + /// state. The default value is `true`. public var dismissesInStages: Bool { return configuration.dismissesInStages } + /// Whether or not the drawer can be dragged up and down. The default value is `true`. public var isDrawerDraggable: Bool { return configuration.isDrawerDraggable } + /// Whether or not the drawer can be dismissed by tapping anywhere outside of it. + /// The default value is `true`. public var isDismissableByOutsideDrawerTaps: Bool { return configuration.isDismissableByOutsideDrawerTaps } + /// How many taps are required for dismissing the drawer by tapping outside of it. + /// The default value is 1. public var numberOfTapsForOutsideDrawerDismissal: Int { return configuration.numberOfTapsForOutsideDrawerDismissal } + /// How fast one needs to "flick" the drawer up or down to make it ignore the + /// partially expanded state. Flicking fast enough up always presents to full screen + /// and flicking fast enough down always collapses the drawer. A typically good value + /// is around 3 points per screen height per second, and that is also the default + /// value of this property. public var flickSpeedThreshold: CGFloat { return configuration.flickSpeedThreshold } + /// There is a band around the partially expanded position of the drawer where + /// ending a drag inside will cause the drawer to move back to the partially + /// expanded position (subjected to the conditions set by `supportsPartialExpansion` + /// and `dismissesInStages`, of course). Set `inDebugMode` to `true` to see lines + /// drawn at those positions. This value represents the gap *above* the partially + /// expanded position. The default value is 40 points. public var upperMarkGap: CGFloat { return configuration.upperMarkGap } + /// There is a band around the partially expanded position of the drawer where + /// ending a drag inside will cause the drawer to move back to the partially + /// expanded position (subjected to the conditions set by `supportsPartialExpansion` + /// and `dismissesInStages`, of course). Set `inDebugMode` to `true` to see lines + /// drawn at those positions. This value represents the gap *below* the partially + /// expanded position. The default value is 40 points. public var lowerMarkGap: CGFloat { return configuration.lowerMarkGap } + /// The animating drawer also animates the radius of its top left and top right + /// corners, from 0 to the value of this property. Setting this to 0 prevents any + /// corner animations from taking place. The default value is 15 points. public var maximumCornerRadius: CGFloat { return configuration.maximumCornerRadius } From d6734eb68067e2c0cdb32ec9529059d139615137 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Sun, 15 Oct 2017 17:33:54 +0100 Subject: [PATCH 58/85] Preliminary version of README. WIP. --- README.md | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/README.md b/README.md index 3ffe1d4..7525cb8 100644 --- a/README.md +++ b/README.md @@ -1 +1,69 @@ # DrawerKit + +```swift + /// How long the animations that move the drawer up and down last. + /// The default value is 0.8 seconds. + public var durationInSeconds: TimeInterval + + /// The type of timing curve to use for the animations. The full set + /// of cubic Bezier curves and spring-based curves is supported. Note + /// that selecting a spring-based timing curve causes the `durationInSeconds` + /// parameter to be ignored, because the duration is computed based on the + /// specifics of the spring-based curve. The default is `UISpringTimingParameters()`, + /// which is the system's global spring-based timing curve. + public var timingCurveProvider: UITimingCurveProvider + + /// When `true`, the drawer is presented first in its partially expanded state. + /// When `false`, the presentation is always to full screen and there is no + /// partially expanded state. The default value is `true`. + public var supportsPartialExpansion: Bool + + /// When `true`, dismissing the drawer from its fully expanded state can result + /// in the drawer stopping at its partially expanded state. When `false`, the + /// dismissal is always straight to the collapsed state. Note that + /// `supportsPartialExpansion` being `false` implies `dismissesInStages` being + /// `false` as well but you can have `supportsPartialExpansion == true` and + /// `dismissesInStages == false`, which would result in presentations to the + /// partially expanded state but all dismissals would be straight to the collapsed + /// state. The default value is `true`. + public var dismissesInStages: Bool + + /// Whether or not the drawer can be dragged up and down. The default value is `true`. + public var isDrawerDraggable: Bool + + /// Whether or not the drawer can be dismissed by tapping anywhere outside of it. + /// The default value is `true`. + public var isDismissableByOutsideDrawerTaps: Bool + + /// How many taps are required for dismissing the drawer by tapping outside of it. + /// The default value is 1. + public var numberOfTapsForOutsideDrawerDismissal: Int + + /// How fast one needs to "flick" the drawer up or down to make it ignore the + /// partially expanded state. Flicking fast enough up always presents to full screen + /// and flicking fast enough down always collapses the drawer. A typically good value + /// is around 3 points per screen height per second, and that is also the default + /// value of this property. + public var flickSpeedThreshold: CGFloat + + /// There is a band around the partially expanded position of the drawer where + /// ending a drag inside will cause the drawer to move back to the partially + /// expanded position (subjected to the conditions set by `supportsPartialExpansion` + /// and `dismissesInStages`, of course). Set `inDebugMode` to `true` to see lines + /// drawn at those positions. This value represents the gap *above* the partially + /// expanded position. The default value is 40 points. + public var upperMarkGap: CGFloat + + /// There is a band around the partially expanded position of the drawer where + /// ending a drag inside will cause the drawer to move back to the partially + /// expanded position (subjected to the conditions set by `supportsPartialExpansion` + /// and `dismissesInStages`, of course). Set `inDebugMode` to `true` to see lines + /// drawn at those positions. This value represents the gap *below* the partially + /// expanded position. The default value is 40 points. + public var lowerMarkGap: CGFloat + + /// The animating drawer also animates the radius of its top left and top right + /// corners, from 0 to the value of this property. Setting this to 0 prevents any + /// corner animations from taking place. The default value is 15 points. + public var maximumCornerRadius: CGFloat +``` From a1ff7d74ad1d784be3a6789fe0ee65a87f4205fc Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Sun, 15 Oct 2017 17:44:24 +0100 Subject: [PATCH 59/85] Removed unused assets. --- .../Assets.xcassets/first.imageset/Contents.json | 12 ------------ .../Assets.xcassets/first.imageset/first.pdf | Bin 2465 -> 0 bytes .../second.imageset/Contents.json | 12 ------------ .../Assets.xcassets/second.imageset/second.pdf | Bin 2423 -> 0 bytes 4 files changed, 24 deletions(-) delete mode 100644 DrawerKitDemo/DrawerKitDemo/Assets.xcassets/first.imageset/Contents.json delete mode 100644 DrawerKitDemo/DrawerKitDemo/Assets.xcassets/first.imageset/first.pdf delete mode 100644 DrawerKitDemo/DrawerKitDemo/Assets.xcassets/second.imageset/Contents.json delete mode 100644 DrawerKitDemo/DrawerKitDemo/Assets.xcassets/second.imageset/second.pdf diff --git a/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/first.imageset/Contents.json b/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/first.imageset/Contents.json deleted file mode 100644 index 33a7451..0000000 --- a/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/first.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "first.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/first.imageset/first.pdf b/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/first.imageset/first.pdf deleted file mode 100644 index 47d911dea647d55983671ead4d08b6f6b3600715..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2465 zcmai03se(l7FLvisY+L#f-EBsK`11Xkc1=%MIujmB|xQ!>5vQ%APFP`Mlf5nSPOKE z2-^C>7DZkM!J;Da5{kfrC?Kvp3J41-3shFXvhuJqKo*oer*r0H{{P-P_x0cTzLzjD!bE z0qT;#Q7l4Gy%fMoXJaKT`@{5#R(MOqJPwQifv8iK6A%Ot9L14h2`38T!2s4PM=1!< zmL06}VYAA|ay#jZRs>HpA%X+eQW4rufWU%d1w5GTy!X#LeZsF_+~ccZmn3Fi)v^Z; zIG;?uU*yLLEYs61tjD>gXOFvSWsh{48xJvPNqKrIJtMdCz2cA2aC7TF?b@K`V!Lw- zE;zpH&ApqhoRjAHt}gK}>(qAc8dvrkD31*`<5<%C(&4_LqhQ3-GOf8~n^leG?+-@@^pjPa$J2gW@O)!b9hdTJ zTauyIJ&~rqeEZC1p9dWgx7{_WRc2=drMO=wcT7B{Zd58z`d)r_36r4V<6PGkCjpGfbnVMtm@;b}F!T?)Qav$_y7H5T;Mf!T}M zWxP9TNqrV?e5;b|pWd3^u2KAatk&3aDB@0 zZBaQNdCZ2#fblzYZnRCCjQ-GQWb-s8bX&<)?SxnUGdDYVFVk`xIf7@g3~$DX`H71_;r5OSZz1p`$_8y_9 z{;MJJ+n=>7Ewg;GnGHoz)&ID0z@F2!e$F7cWQ?d6s(!VY)_Gw})xCyMvsD={5i&H* zAIr_ACo8;Se6<*!-mm9Am79Iz^RVlc?%S5sg|E*SyIV{dd9{Mpf#d3cih5WKt=%ps zBEo)bt8EjmeCFYJRYU|b7d`p+-V|X2wOCYtyLP6t=!WH-kgdf0A};ytPfZiCwVPx{ z`g;zpe{8a4RQxQUwVU02<4X3w|9h;}XjhGWquqn{vVqq8g{*}BQF9=ybe_TM*!M=rFs1pN)yzctIXAoicsCe6>fit>wgZ#vp^hZOY0`J`rx zwdSK?GwR_xm9;5XjH|vf{O+Yg-)z;s*xt>;-vU`D-_SK<=~4eEVQkV~57{vyH6ebu>DqwE~Mx_A31Lg~$ zy`i52ew?GEluE%AN>ju*5z~dx2QQLP-G+E>o|En2+s&<^4*d zWD+<8PXs_Jp7_5Dpi(Gg3J?M;%Rn0{dcxQRh!ip+iHJ_e`!bM1MGxwI8Iedr+x>wI zq@&04fsADHfBccq^gpm8Q_u-~Uj|Z$YwSR(^_mzUb!}Xb2Cj`uqphg}v_Y%?Fs=;= zWq3s{hy)Tu!ji@RJsd$C+G{F6V2j1*Xk+7xo|LO7M2yb)(wc=%x-AclP9pLsbTF7m tf?*P!2EjJu5MD57P32Q4pdJ3tO_shCDT2=K;$ebx8ymcd$v!V9{2$sQiO>K5 diff --git a/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/second.imageset/Contents.json b/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/second.imageset/Contents.json deleted file mode 100644 index 03bd9c9..0000000 --- a/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/second.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "second.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/second.imageset/second.pdf b/DrawerKitDemo/DrawerKitDemo/Assets.xcassets/second.imageset/second.pdf deleted file mode 100644 index 401614e288b4b160471c2776bed6f09762af3e1c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2423 zcmai02~-nz8dqq7rWU#)x2z)&iMWtV?gK$2a)?|BP!TbkV}J-Gl1wmy+3G9S0=uOY zwCjai6uBOPMMdNiN`VDYKwLQr2#W^`R93*Ua=0^)EGYY4=grIf|KIohziYnv{elnR zdN_hk6db<(+3RPcxs!iut8c(j0TLh=9mY931H^rhEE0|aXo!dh5Iv+ZFr+}9F+v#P zLSnfD0$f~hN>~92V{z)WJA&2)5rW!|I?;o?oI*%zejJiuY>hFLQwKlAa9O+PnPh#% zJp~JAfs-De6u@;{cREMs}D-sGib_4>50m8_|Uj-zC0m@Qx zJV+^5Da4Qxpf3sBtKZs<_aI$Cb1T+h<->yL^rfiGNelYkqgC3#SqVP_iy*_RPcbjH)xYf zvYdC-)D_t8c{=5%;$VJQxs_#61J*kuXMC?|&PbPo-7uSxoNr~&J94|gKk+03JGXXM zx4w~=^VUzWqQveu_ilRgPdgNNdnLZCH`=;w91}cP5ihdm*B_jI+rk=Pz2Lk!)XUu| zkls3P5&YHJ!t0kF+MUVBHOAx)a zy|JxHNd37z*~b5#%*0w_F|#4?erNP;pG`Fevoq2%?9Y>%Cmzv1(ks;aPIGaiy1mhU zr{7lB^Mem&`QK{b{uiDC zLD8muY(FNk{YdV|zO#o_VB%);8|~Bh(Z4p?GM}g-HN2;q7~YNl-lD~HZ$taYw$Fzbn6c?c5eo=@`i>()el#qwB)hI-gp0 ztsw@dh-I5NJkk)RCoBC`L8ffKfY)=oG=#4LA9TO6eA}9~@a6dupB+=S{%s&NY}^0GY`hAqod)uxXHitrN%bWOJohV>t?!&Z#d4y^7J@=Oh`HV zee**4j>xRUd)|D0^I-B#<`m1-5PO{)!v(q?ecFYqr_8Q{+z7#n0uFbq%_?hZ-Ck(6NRn=vDS7Nb1Xxx_)9`!i- zGR>av*PJ+P_AGO*a_$FbtCCISg5PK44p&W#t$)+HVcxpR9LAL%_Pv$p`0tO*sYZD0 zCG3-ipr4WoVbjpw%Nf^r^rnWcDcjumgBPEjO;NM6QtZ8wU#0rjHx5zs%CahF4$W*U zxu!q8kYP+>v!BNPVIRJUGIWU@7qVvx2VZDw4gV8%z%bjpF#aF(W|PmliwlakuRq>= zhaCRc^R!7T)#Twb0{!Tns=CxGcGX|leErg`f3|v0eBXxHU&6Sj-_>zOL!!d+j5Xt* zPS;rWd^zEF{5d5%)nNK=)zFJ8Uc=aQKD%UK)_TNes1Gh{SD*Mb_@O` z7*zs9Uq~Vq?v|^8FcM-yLGMKzR3YFB0z@CFM2X-dio%Gu6&MJj=I?uS7sA3AdE^p8 zE49$PjJzPIJwfn;jso7X2#jynr3dO@p5QAK1O9wqKLC=PkQjj~p#n|-5O`ihbTzzu zL{}2QEe28pJYfVZRW56FL`LvO&|C^1Th{0}hQMW$Ng$QPpnzlwnMnf=kVy8(y==L% zLMa>r0gELdT Date: Mon, 16 Oct 2017 02:20:17 +0100 Subject: [PATCH 60/85] Added some pseudo-code to the README, to explain the presentation/dismissal logic. --- README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/README.md b/README.md index 7525cb8..69d87ea 100644 --- a/README.md +++ b/README.md @@ -67,3 +67,31 @@ /// corner animations from taking place. The default value is 15 points. public var maximumCornerRadius: CGFloat ``` + + +```swift + if isMovingUpQuickly { show fully expanded } + if isMovingDownQuickly { collapse all the way (ie, dismiss) } + + if isAboveUpperMark { + if isMovingUp || isNotMoving { + show fully expanded + } else { // is moving down + collapse to the partially expanded state or all the way (ie, dismiss), + depending on the values of `supportsPartialExpansion` and `dismissesInStages` + } + } + + if isAboveLowerMark { // ie, in the band surrounding the partially expanded state + if isMovingDown { + collapse all the way (ie, dismiss) + } else { // not moving or moving up + expand to the partially expanded state or all the way (ie, full-screen), + depending on the value of `supportsPartialExpansion` + } + } + + // below the band surrounding the partially expanded state + collapse all the way (ie, dismiss) + } +``` From 882795be33f20b6e9cd094ee3bf0fe66da290000 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Mon, 16 Oct 2017 02:25:11 +0100 Subject: [PATCH 61/85] Code style change. --- .../DrawerKit/Internal API/PresentationController.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/DrawerKit/DrawerKit/Internal API/PresentationController.swift b/DrawerKit/DrawerKit/Internal API/PresentationController.swift index b3a069f..b8b0efe 100644 --- a/DrawerKit/DrawerKit/Internal API/PresentationController.swift +++ b/DrawerKit/DrawerKit/Internal API/PresentationController.swift @@ -7,8 +7,10 @@ final class PresentationController: UIPresentationController { private var presentedViewDragGR: UIPanGestureRecognizer? private let inDebugMode: Bool - init(presentingVC: UIViewController?, presentedVC: UIViewController, - configuration: DrawerConfiguration, inDebugMode: Bool = false) { + init(presentingVC: UIViewController?, + presentedVC: UIViewController, + configuration: DrawerConfiguration, + inDebugMode: Bool = false) { self.configuration = configuration self.inDebugMode = inDebugMode super.init(presentedViewController: presentedVC, presenting: presentingVC) From d096649d26ce8283badcaa73e844a8819cadba85 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Mon, 16 Oct 2017 02:30:05 +0100 Subject: [PATCH 62/85] Removed unnecessary [weak self] capture lists. --- .../Internal API/PresentationController.swift | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/DrawerKit/DrawerKit/Internal API/PresentationController.swift b/DrawerKit/DrawerKit/Internal API/PresentationController.swift index b8b0efe..3b649e9 100644 --- a/DrawerKit/DrawerKit/Internal API/PresentationController.swift +++ b/DrawerKit/DrawerKit/Internal API/PresentationController.swift @@ -179,22 +179,22 @@ private extension PresentationController { let maxCornerRadius = maximumCornerRadius let endingCornerRadius = cornerRadius(at: endPosY) - animator.addAnimations { [weak self] in - self?.currentDrawerY = endPosY + animator.addAnimations { + self.currentDrawerY = endPosY if maxCornerRadius > 0 { - self?.currentDrawerCornerRadius = endingCornerRadius + self.currentDrawerCornerRadius = endingCornerRadius } } if endPosY == containerViewH { - animator.addCompletion { [weak self] _ in - self?.presentedViewController.dismiss(animated: true) + animator.addCompletion { _ in + self.presentedViewController.dismiss(animated: true) } } if maxCornerRadius > 0 && endPosY != drawerPartialY { - animator.addCompletion { [weak self] _ in - self?.currentDrawerCornerRadius = 0 + animator.addCompletion { _ in + self.currentDrawerCornerRadius = 0 } } @@ -213,13 +213,13 @@ private extension PresentationController { timingParameters: timingCurveProvider) let endingCornerRadius = cornerRadius(at: endPosY) - animator.addAnimations { [weak self] in - self?.currentDrawerCornerRadius = endingCornerRadius + animator.addAnimations { + self.currentDrawerCornerRadius = endingCornerRadius } if endPosY != drawerPartialY { - animator.addCompletion { [weak self] _ in - self?.currentDrawerCornerRadius = 0 + animator.addCompletion { _ in + self.currentDrawerCornerRadius = 0 } } From f590dc8345436690454cb53ccb7b791e732e2ae1 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Mon, 16 Oct 2017 03:53:27 +0100 Subject: [PATCH 63/85] Changed default value of durationInSeconds from 0.8 to 0.3 seconds. --- DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift b/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift index 9a53b46..e497957 100644 --- a/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift +++ b/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift @@ -21,7 +21,7 @@ public struct DrawerConfiguration: Equatable { public var maximumCornerRadius: CGFloat - public init(durationInSeconds: TimeInterval = 0.8, + public init(durationInSeconds: TimeInterval = 0.3, timingCurveProvider: UITimingCurveProvider = UISpringTimingParameters(), supportsPartialExpansion: Bool = true, dismissesInStages: Bool = true, From 59fb58250dc81200a5f57f54f7752b7c42d1be20 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Mon, 16 Oct 2017 11:42:28 +0100 Subject: [PATCH 64/85] Fixed a botched global search/replace in DrawerConfiguration+Equatable. --- .../Internal API/Extensions/DrawerConfiguration+Equatable.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DrawerKit/DrawerKit/Internal API/Extensions/DrawerConfiguration+Equatable.swift b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerConfiguration+Equatable.swift index f18fb33..6d965ee 100644 --- a/DrawerKit/DrawerKit/Internal API/Extensions/DrawerConfiguration+Equatable.swift +++ b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerConfiguration+Equatable.swift @@ -10,7 +10,7 @@ extension DrawerConfiguration { && lhs.isDismissableByOutsideDrawerTaps == rhs.isDismissableByOutsideDrawerTaps && lhs.numberOfTapsForOutsideDrawerDismissal == rhs.numberOfTapsForOutsideDrawerDismissal && lhs.flickSpeedThreshold == rhs.flickSpeedThreshold - && lhs.lowerMarkGap == rhs.lowerMarkGap + && lhs.upperMarkGap == rhs.upperMarkGap && lhs.lowerMarkGap == rhs.lowerMarkGap && lhs.maximumCornerRadius == rhs.maximumCornerRadius } From 561b449afcd1d36e82c9052a1a76f4c52e7e541c Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Mon, 16 Oct 2017 11:50:56 +0100 Subject: [PATCH 65/85] Renamed evil local gr variables to less evil names. --- .../Internal API/InteractionController.swift | 20 ++++----- .../Internal API/PresentationController.swift | 42 +++++++++---------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/DrawerKit/DrawerKit/Internal API/InteractionController.swift b/DrawerKit/DrawerKit/Internal API/InteractionController.swift index e025301..7150ddf 100644 --- a/DrawerKit/DrawerKit/Internal API/InteractionController.swift +++ b/DrawerKit/DrawerKit/Internal API/InteractionController.swift @@ -27,22 +27,22 @@ extension InteractionController { private extension InteractionController { func setupPresentedViewDragRecogniser() { guard presentedViewDragGR == nil else { return } - let gr = UIPanGestureRecognizer(target: self, - action: #selector(handlePresentedViewDrag)) - presentedVC.view.addGestureRecognizer(gr) - presentedViewDragGR = gr + let panGesture = UIPanGestureRecognizer(target: self, + action: #selector(handlePresentedViewDrag)) + presentedVC.view.addGestureRecognizer(panGesture) + presentedViewDragGR = panGesture } func removePresentedViewDragRecogniser() { - guard let gr = presentedViewDragGR else { return } - presentedVC.view.removeGestureRecognizer(gr) + guard let panGesture = presentedViewDragGR else { return } + presentedVC.view.removeGestureRecognizer(panGesture) presentedViewDragGR = nil } @objc func handlePresentedViewDrag() { - guard let gr = presentedViewDragGR, let view = gr.view else { return } + guard let panGesture = presentedViewDragGR, let view = panGesture.view else { return } - switch gr.state { + switch panGesture.state { case .began: if isPresentation { presentingVC.present(presentedVC, animated: true) @@ -51,8 +51,8 @@ private extension InteractionController { } case .changed: - let offsetY = gr.translation(in: view).y - gr.setTranslation(.zero, in: view) + let offsetY = panGesture.translation(in: view).y + panGesture.setTranslation(.zero, in: view) update(percentComplete + offsetY / containerH) case .ended: diff --git a/DrawerKit/DrawerKit/Internal API/PresentationController.swift b/DrawerKit/DrawerKit/Internal API/PresentationController.swift index 3b649e9..e637094 100644 --- a/DrawerKit/DrawerKit/Internal API/PresentationController.swift +++ b/DrawerKit/DrawerKit/Internal API/PresentationController.swift @@ -99,23 +99,23 @@ private extension PresentationController { let isDismissable = isDismissableByOutsideDrawerTaps let numTapsRequired = numberOfTapsForOutsideDrawerDismissal guard isDismissable && numTapsRequired > 0 else { return } - let gr = UITapGestureRecognizer(target: self, - action: #selector(handleContainerViewDismissalTap)) - gr.numberOfTouchesRequired = 1 - gr.numberOfTapsRequired = numTapsRequired - containerView?.addGestureRecognizer(gr) - containerViewDismissalTapGR = gr + let tapGesture = UITapGestureRecognizer(target: self, + action: #selector(handleContainerViewDismissalTap)) + tapGesture.numberOfTouchesRequired = 1 + tapGesture.numberOfTapsRequired = numTapsRequired + containerView?.addGestureRecognizer(tapGesture) + containerViewDismissalTapGR = tapGesture } func removeContainerViewDismissalTapRecogniser() { - guard let gr = containerViewDismissalTapGR else { return } - containerView?.removeGestureRecognizer(gr) + guard let tapGesture = containerViewDismissalTapGR else { return } + containerView?.removeGestureRecognizer(tapGesture) containerViewDismissalTapGR = nil } @objc func handleContainerViewDismissalTap() { - guard let gr = containerViewDismissalTapGR else { return } - let tapY = gr.location(in: containerView).y + guard let tapGesture = containerViewDismissalTapGR else { return } + let tapY = tapGesture.location(in: containerView).y guard tapY < currentDrawerY else { return } presentedViewController.dismiss(animated: true) } @@ -125,35 +125,35 @@ private extension PresentationController { func setupPresentedViewDragRecogniser() { guard presentedViewDragGR == nil else { return } guard isDrawerDraggable else { return } - let gr = UIPanGestureRecognizer(target: self, - action: #selector(handlePresentedViewDrag)) - presentedView?.addGestureRecognizer(gr) - presentedViewDragGR = gr + let panGesture = UIPanGestureRecognizer(target: self, + action: #selector(handlePresentedViewDrag)) + presentedView?.addGestureRecognizer(panGesture) + presentedViewDragGR = panGesture } func removePresentedViewDragRecogniser() { - guard let gr = presentedViewDragGR else { return } - presentedView?.removeGestureRecognizer(gr) + guard let panGesture = presentedViewDragGR else { return } + presentedView?.removeGestureRecognizer(panGesture) presentedViewDragGR = nil } @objc func handlePresentedViewDrag() { - guard let gr = presentedViewDragGR, let view = gr.view else { return } + guard let panGesture = presentedViewDragGR, let view = panGesture.view else { return } - switch gr.state { + switch panGesture.state { case .began: lastDrawerY = currentDrawerY case .changed: lastDrawerY = currentDrawerY - let offsetY = gr.translation(in: view).y - gr.setTranslation(.zero, in: view) + let offsetY = panGesture.translation(in: view).y + panGesture.setTranslation(.zero, in: view) let positionY = currentDrawerY + offsetY currentDrawerY = min(max(positionY, 0), containerViewH) currentDrawerCornerRadius = cornerRadius(at: currentDrawerY) case .ended: - let drawerVelocityY = gr.velocity(in: view).y / containerViewH + let drawerVelocityY = panGesture.velocity(in: view).y / containerViewH let endPosY = endingPositionY(positionY: currentDrawerY, velocityY: drawerVelocityY) animateTransition(to: endPosY) From 557798a5510edbbae36320b64de9d9ec69a8b3fa Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Mon, 16 Oct 2017 11:56:14 +0100 Subject: [PATCH 66/85] Fixed guard statements as per comments. --- .../Internal API/InteractionController.swift | 5 ++++- .../Internal API/PresentationController.swift | 12 ++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/DrawerKit/DrawerKit/Internal API/InteractionController.swift b/DrawerKit/DrawerKit/Internal API/InteractionController.swift index 7150ddf..5244f0b 100644 --- a/DrawerKit/DrawerKit/Internal API/InteractionController.swift +++ b/DrawerKit/DrawerKit/Internal API/InteractionController.swift @@ -40,7 +40,10 @@ private extension InteractionController { } @objc func handlePresentedViewDrag() { - guard let panGesture = presentedViewDragGR, let view = panGesture.view else { return } + guard + let panGesture = presentedViewDragGR, + let view = panGesture.view + else { return } switch panGesture.state { case .began: diff --git a/DrawerKit/DrawerKit/Internal API/PresentationController.swift b/DrawerKit/DrawerKit/Internal API/PresentationController.swift index e637094..a3e284f 100644 --- a/DrawerKit/DrawerKit/Internal API/PresentationController.swift +++ b/DrawerKit/DrawerKit/Internal API/PresentationController.swift @@ -123,8 +123,7 @@ private extension PresentationController { private extension PresentationController { func setupPresentedViewDragRecogniser() { - guard presentedViewDragGR == nil else { return } - guard isDrawerDraggable else { return } + guard presentedViewDragGR == nil && isDrawerDraggable else { return } let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePresentedViewDrag)) presentedView?.addGestureRecognizer(panGesture) @@ -202,9 +201,7 @@ private extension PresentationController { } func addCornerRadiusAnimationEnding(at endPositionY: CGFloat, clamping: Bool = false) { - guard maximumCornerRadius > 0 else { return } - guard drawerPartialY > 0 else { return } - guard endPositionY != currentDrawerY else { return } + guard maximumCornerRadius > 0 && drawerPartialY > 0 && endPositionY != currentDrawerY else { return } let endPosY = (clamping ? clamped(endPositionY) : endPositionY) guard endPosY != currentDrawerY else { return } @@ -294,9 +291,8 @@ private extension PresentationController { private extension PresentationController { func setupDebugHeightMarks() { - guard inDebugMode else { return } - guard let containerView = containerView else { return } - guard upperMarkGap > 0 || lowerMarkGap > 0 else { return } + guard inDebugMode && (upperMarkGap > 0 || lowerMarkGap > 0), + let containerView = containerView else { return } if upperMarkGap > 0 { let upperMarkYView = UIView() From c3b09d68094b75c956cb9de4773f2fa81230231e Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Mon, 16 Oct 2017 11:58:08 +0100 Subject: [PATCH 67/85] Removed a comment. --- DrawerKit/DrawerKit/Internal API/PresentationController.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/DrawerKit/DrawerKit/Internal API/PresentationController.swift b/DrawerKit/DrawerKit/Internal API/PresentationController.swift index a3e284f..f160429 100644 --- a/DrawerKit/DrawerKit/Internal API/PresentationController.swift +++ b/DrawerKit/DrawerKit/Internal API/PresentationController.swift @@ -248,7 +248,6 @@ private extension PresentationController { let isNotMoving = (velocityY == 0) let isMovingUp = (velocityY < 0) // recall that Y-axis points down let isMovingDown = (velocityY > 0) - // flickSpeedThreshold == 0 disables speed-dependence let isMovingQuickly = (flickSpeedThreshold > 0) && (abs(velocityY) > flickSpeedThreshold) let isMovingUpQuickly = isMovingUp && isMovingQuickly let isMovingDownQuickly = isMovingDown && isMovingQuickly From d42f7c7f7affb63e1f00bbb290da9efb1eb36863 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Mon, 16 Oct 2017 12:04:49 +0100 Subject: [PATCH 68/85] Removed a protocol extension. --- DrawerKit/DrawerKit/Public API/DrawerPresentable.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/DrawerKit/DrawerKit/Public API/DrawerPresentable.swift b/DrawerKit/DrawerKit/Public API/DrawerPresentable.swift index fa9fb07..8d386ad 100644 --- a/DrawerKit/DrawerKit/Public API/DrawerPresentable.swift +++ b/DrawerKit/DrawerKit/Public API/DrawerPresentable.swift @@ -4,9 +4,3 @@ import UIKit public protocol DrawerPresentable: class { var heightOfPartiallyExpandedDrawer: CGFloat { get } } - -public extension DrawerPresentable where Self: UIViewController { - public var heightOfPartiallyExpandedDrawer: CGFloat { - return 0 - } -} From 60085af9a11f6416e1c498fa4354d02bbc98065b Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Mon, 16 Oct 2017 12:05:09 +0100 Subject: [PATCH 69/85] Make sure that the height of the partially expanded drawer is non-negative. --- DrawerKit/DrawerKit/Internal API/PresentationController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DrawerKit/DrawerKit/Internal API/PresentationController.swift b/DrawerKit/DrawerKit/Internal API/PresentationController.swift index f160429..a1ff2c7 100644 --- a/DrawerKit/DrawerKit/Internal API/PresentationController.swift +++ b/DrawerKit/DrawerKit/Internal API/PresentationController.swift @@ -59,7 +59,7 @@ private extension PresentationController { var drawerPartialH: CGFloat { guard let presentedVC = presentedViewController as? DrawerPresentable else { return 0 } - return presentedVC.heightOfPartiallyExpandedDrawer + return max(0, presentedVC.heightOfPartiallyExpandedDrawer) } var drawerPartialY: CGFloat { From e4fa1c779e32f71adc384b29f864a0143c39e3a7 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Mon, 16 Oct 2017 12:16:35 +0100 Subject: [PATCH 70/85] Added documentation to all exposed public entities. --- .../Public API/DrawerConfiguration.swift | 51 +++++++++++++++++++ .../Public API/DrawerDisplayController.swift | 17 ++++++- .../Public API/DrawerPresentable.swift | 2 + .../Public API/DrawerPresenting.swift | 3 ++ 4 files changed, 72 insertions(+), 1 deletion(-) diff --git a/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift b/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift index e497957..6857054 100644 --- a/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift +++ b/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift @@ -4,23 +4,74 @@ import UIKit /// See `DrawerDisplayController+Configuration.swift` for documentation on /// what the various parameters are used for. public struct DrawerConfiguration: Equatable { + /// How long the animations that move the drawer up and down last. + /// The default value is 0.8 seconds. public var durationInSeconds: TimeInterval + + /// The type of timing curve to use for the animations. The full set + /// of cubic Bezier curves and spring-based curves is supported. Note + /// that selecting a spring-based timing curve causes the `durationInSeconds` + /// parameter to be ignored, because the duration is computed based on the + /// specifics of the spring-based curve. The default is `UISpringTimingParameters()`, + /// which is the system's global spring-based timing curve. public var timingCurveProvider: UITimingCurveProvider + /// When `true`, the drawer is presented first in its partially expanded state. + /// When `false`, the presentation is always to full screen and there is no + /// partially expanded state. The default value is `true`. public var supportsPartialExpansion: Bool + + /// When `true`, dismissing the drawer from its fully expanded state can result + /// in the drawer stopping at its partially expanded state. When `false`, the + /// dismissal is always straight to the collapsed state. Note that + /// `supportsPartialExpansion` being `false` implies `dismissesInStages` being + /// `false` as well but you can have `supportsPartialExpansion == true` and + /// `dismissesInStages == false`, which would result in presentations to the + /// partially expanded state but all dismissals would be straight to the collapsed + /// state. The default value is `true`. public var dismissesInStages: Bool + + /// Whether or not the drawer can be dragged up and down. The default value is `true`. public var isDrawerDraggable: Bool + /// Whether or not the drawer can be dismissed by tapping anywhere outside of it. + /// The default value is `true`. public var isDismissableByOutsideDrawerTaps: Bool + + /// How many taps are required for dismissing the drawer by tapping outside of it. + /// The default value is 1. public var numberOfTapsForOutsideDrawerDismissal: Int + /// How fast one needs to "flick" the drawer up or down to make it ignore the + /// partially expanded state. Flicking fast enough up always presents to full screen + /// and flicking fast enough down always collapses the drawer. A typically good value + /// is around 3 points per screen height per second, and that is also the default + /// value of this property. public var flickSpeedThreshold: CGFloat + /// There is a band around the partially expanded position of the drawer where + /// ending a drag inside will cause the drawer to move back to the partially + /// expanded position (subjected to the conditions set by `supportsPartialExpansion` + /// and `dismissesInStages`, of course). Set `inDebugMode` to `true` to see lines + /// drawn at those positions. This value represents the gap *above* the partially + /// expanded position. The default value is 40 points. public var upperMarkGap: CGFloat + + /// There is a band around the partially expanded position of the drawer where + /// ending a drag inside will cause the drawer to move back to the partially + /// expanded position (subjected to the conditions set by `supportsPartialExpansion` + /// and `dismissesInStages`, of course). Set `inDebugMode` to `true` to see lines + /// drawn at those positions. This value represents the gap *below* the partially + /// expanded position. The default value is 40 points. public var lowerMarkGap: CGFloat + /// The animating drawer also animates the radius of its top left and top right + /// corners, from 0 to the value of this property. Setting this to 0 prevents any + /// corner animations from taking place. The default value is 15 points. public var maximumCornerRadius: CGFloat + + /// Initialiser for `DrawerConfiguration`. public init(durationInSeconds: TimeInterval = 0.3, timingCurveProvider: UITimingCurveProvider = UISpringTimingParameters(), supportsPartialExpansion: Bool = true, diff --git a/DrawerKit/DrawerKit/Public API/DrawerDisplayController.swift b/DrawerKit/DrawerKit/Public API/DrawerDisplayController.swift index 2edab97..c24bae8 100755 --- a/DrawerKit/DrawerKit/Public API/DrawerDisplayController.swift +++ b/DrawerKit/DrawerKit/Public API/DrawerDisplayController.swift @@ -5,14 +5,29 @@ import UIKit // - support insufficiently tall content // - support not-covering status bar and/or having a gap at the top +/// Instances of this class are returned as part of the `DrawerPresenting` protocol. public final class DrawerDisplayController: NSObject { - public let configuration: DrawerConfiguration // intentionally immutable + /// The collection of configurable parameters dictating how the drawer works. + public let configuration: DrawerConfiguration weak var presentingVC: (UIViewController & DrawerPresenting)? /* strong */ var presentedVC: (UIViewController & DrawerPresentable) let inDebugMode: Bool + /// Initialiser for `DrawerDisplayController`. + /// + /// - Parameters: + /// - presentingViewController: + /// the view controller presenting the drawer. + /// - presentedViewController: + /// the view controller wanting to be presented as a drawer. + /// - configuration: + /// the collection of configurable parameters dictating how the drawer works. + /// - inDebugMode: + /// a boolean value which, when true, draws guiding lines on top of the + /// presenting view controller but below the presented view controller. + /// Its default value is false. public init(presentingViewController: (UIViewController & DrawerPresenting), presentedViewController: (UIViewController & DrawerPresentable), configuration: DrawerConfiguration = DrawerConfiguration(), diff --git a/DrawerKit/DrawerKit/Public API/DrawerPresentable.swift b/DrawerKit/DrawerKit/Public API/DrawerPresentable.swift index 8d386ad..86bf4fa 100644 --- a/DrawerKit/DrawerKit/Public API/DrawerPresentable.swift +++ b/DrawerKit/DrawerKit/Public API/DrawerPresentable.swift @@ -2,5 +2,7 @@ import UIKit /// Protocol that view controllers presented inside a drawer must conform to. public protocol DrawerPresentable: class { + /// The height at which the drawer must be presented when it's in its + /// partially expanded state. If negative, its value is clamped to zero. var heightOfPartiallyExpandedDrawer: CGFloat { get } } diff --git a/DrawerKit/DrawerKit/Public API/DrawerPresenting.swift b/DrawerKit/DrawerKit/Public API/DrawerPresenting.swift index 207d174..661f91f 100644 --- a/DrawerKit/DrawerKit/Public API/DrawerPresenting.swift +++ b/DrawerKit/DrawerKit/Public API/DrawerPresenting.swift @@ -2,5 +2,8 @@ import UIKit /// Protocol that view controllers presenting a drawer must conform to. public protocol DrawerPresenting: class { + /// An object vended by the presenting view controller, whose responsibility + /// is to coordinate the presentation, animation, and interactivity of/with + /// the drawer. var drawerDisplayController: DrawerDisplayController? { get } } From 6a3e3fde6481a3425b497336cb9e3a060f7a3fe2 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Mon, 16 Oct 2017 12:24:16 +0100 Subject: [PATCH 71/85] Style change as per comments. --- DrawerKit/DrawerKit/Internal API/AnimationController.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DrawerKit/DrawerKit/Internal API/AnimationController.swift b/DrawerKit/DrawerKit/Internal API/AnimationController.swift index 456a232..351757a 100644 --- a/DrawerKit/DrawerKit/Internal API/AnimationController.swift +++ b/DrawerKit/DrawerKit/Internal API/AnimationController.swift @@ -5,7 +5,8 @@ final class AnimationController: NSObject { private let durationInSeconds: TimeInterval private let timingCurveProvider: UITimingCurveProvider - init(isPresentation: Bool, durationInSeconds: TimeInterval, + init(isPresentation: Bool, + durationInSeconds: TimeInterval, timingCurveProvider: UITimingCurveProvider) { self.isPresentation = isPresentation self.durationInSeconds = durationInSeconds From 942318c58ed4fde6089314c1ded164e804707024 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Mon, 16 Oct 2017 14:12:27 +0100 Subject: [PATCH 72/85] Fixed documentation. --- DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift | 2 +- .../Public API/DrawerDisplayController+Configuration.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift b/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift index 6857054..ba1c4df 100644 --- a/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift +++ b/DrawerKit/DrawerKit/Public API/DrawerConfiguration.swift @@ -5,7 +5,7 @@ import UIKit /// what the various parameters are used for. public struct DrawerConfiguration: Equatable { /// How long the animations that move the drawer up and down last. - /// The default value is 0.8 seconds. + /// The default value is 0.3 seconds. public var durationInSeconds: TimeInterval /// The type of timing curve to use for the animations. The full set diff --git a/DrawerKit/DrawerKit/Public API/DrawerDisplayController+Configuration.swift b/DrawerKit/DrawerKit/Public API/DrawerDisplayController+Configuration.swift index b680bf1..5ecae3f 100755 --- a/DrawerKit/DrawerKit/Public API/DrawerDisplayController+Configuration.swift +++ b/DrawerKit/DrawerKit/Public API/DrawerDisplayController+Configuration.swift @@ -4,7 +4,7 @@ import UIKit /// configuration parameters directly from the drawer display controller. extension DrawerDisplayController { /// How long the animations that move the drawer up and down last. - /// The default value is 0.8 seconds. + /// The default value is 0.3 seconds. public var durationInSeconds: TimeInterval { return configuration.durationInSeconds } From eb46380aede1fa0320b0b43ba94e285a6af98f06 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Mon, 16 Oct 2017 14:23:41 +0100 Subject: [PATCH 73/85] Updated README file. --- README.md | 130 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 128 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 69d87ea..843a69c 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,121 @@ # DrawerKit + + +[![Version](https://img.shields.io/cocoapods/v/DrawerKit.svg?style=flat)](http://cocoapods.org/pods/DrawerKit) +[![Platform](https://img.shields.io/cocoapods/p/DrawerKit.svg?style=flat)](http://cocoapods.org/pods/DrawerKit) +[![Swift 4.0.x](https://img.shields.io/badge/Swift-4.0.x-orange.svg)](https://swift.org) +[![Xcode](https://img.shields.io/badge/Xcode-9.x-blue.svg)](https://developer.apple.com/xcode) +[![License](https://img.shields.io/cocoapods/l/DrawerKit.svg?style=flat)](http://cocoapods.org/pods/DrawerKit) + +## What is DrawerKit? +__DrawerKit__ is a custom view controller presentation mimicking the kind of behaviour you see in the Apple Maps app. +It lets any view controller modally present another arbitrary view controller in such a way that the presented content +is only partially shown at first, then allowing the user to interact with it by showing more or less of that content +until it's fully presented or fully dismissed. It's *not* (yet) a complete implementation of the behaviour you see in +the Maps app simply because our specific needs dictated something else. We intend to continue working on it to address +that limitation. + +## What version of iOS does it require or support? + +__DrawerKit__ is compatible with iOS 11 but only requires iOS 10. + +## How to use it? + +In order for the _presenting_ view controller to present another view controller (the _presented_ view controller) +as a drawer, both view controllers need to conform to specific protocols, as follows. + +The presenting view controller needs to conform to the `DrawerPresenting` protocol, + +```swift +public protocol DrawerPresenting: class { + /// An object vended by the presenting view controller, whose responsibility + /// is to coordinate the presentation, animation, and interactivity of/with + /// the drawer. + var drawerDisplayController: DrawerDisplayController? { get } +} +``` + +and the presented view controller needs to conform to the `DrawerPresentable` protocol, + +```swift +public protocol DrawerPresentable: class { + /// The height at which the drawer must be presented when it's in its + /// partially expanded state. If negative, its value is clamped to zero. + var heightOfPartiallyExpandedDrawer: CGFloat { get } +} +``` + +After that, it's essentially business as usual in regards to presenting a view controller modally. Here's the basic +code to get a view controller to present another as a drawer: + +```swift +extension PresenterViewController { + func doModalPresentation() { + guard let vc = storyboard?.instantiateViewController(withIdentifier: "presented") + as? PresentedViewController else { return } + + // you can provide the configuration values in the initialiser... + var configuration = DrawerConfiguration(/* ..., ..., ..., */) + + // ... or after initialisation + configuration.durationInSeconds = 0.8 + configuration.timingCurveProvider = UISpringTimingParameters(dampingRatio: 0.8) + configuration.supportsPartialExpansion = true + configuration.dismissesInStages = true + configuration.isDrawerDraggable = true + configuration.isDismissableByOutsideDrawerTaps = true + configuration.numberOfTapsForOutsideDrawerDismissal = 1 + configuration.flickSpeedThreshold = 3 + configuration.upperMarkGap = 30 + configuration.lowerMarkGap = 30 + configuration.maximumCornerRadius = 20 + + drawerDisplayController = DrawerDisplayController(presentingViewController: self, + presentedViewController: vc, + configuration: configuration) + + present(vc, animated: true) + } +} +``` + +and here's one way to implement the corresponding presented view controller: + +```swift +extension PresentedViewController: DrawerPresentable { + var heightOfPartiallyExpandedDrawer: CGFloat { + guard let view = self.view as? PresentedView else { return 0 } + return view.dividerView.frame.origin.y + } +} +``` + +Naturally, the presented view controller can dismiss itself at any time, following the usual approach: + +```swift +extension PresentedViewController { + @IBAction func dismissButtonTapped() { + dismiss(animated: true) + } +} +``` + +## How configurable is it? + +__DrawerKit__ has a number of configurable properties, conveniently collected together into a struct, +`DrawerConfiguration`. Here's a list of all the currently supported configuration options: + ```swift /// How long the animations that move the drawer up and down last. - /// The default value is 0.8 seconds. + /// The default value is 0.3 seconds. public var durationInSeconds: TimeInterval /// The type of timing curve to use for the animations. The full set @@ -68,6 +181,10 @@ public var maximumCornerRadius: CGFloat ``` +## What's the actual drawer behaviour logic? + +The behaviour of how and under what situations the drawer gets fully presented, partially presented, or +collapsed (dismissed) is summarised by the pseudo-code below: ```swift if isMovingUpQuickly { show fully expanded } @@ -93,5 +210,14 @@ // below the band surrounding the partially expanded state collapse all the way (ie, dismiss) - } ``` + +#### CocoaPods + +If you use [CocoaPods][] to manage your dependencies, simply add DrawerKit to your `Podfile`: + +``` +pod 'DrawerKit', '~> 0.0.1' +``` + +[CocoaPods]: https://cocoapods.org/ From a2883fcb4876cbd0d61b4141079a2f00ffcbbef1 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Mon, 16 Oct 2017 14:28:38 +0100 Subject: [PATCH 74/85] Tweaked the README file. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 843a69c..15e280b 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ that limitation. ## What version of iOS does it require or support? -__DrawerKit__ is compatible with iOS 11 but only requires iOS 10. +__DrawerKit__ is compatible with iOS 10 and above. ## How to use it? From 56bc18668340057aa392c172e4d30dbd0a7d2cc8 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Mon, 16 Oct 2017 17:00:20 +0100 Subject: [PATCH 75/85] Decreased the deployment target requirement, from 10.3 to 10.0. --- DrawerKit.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DrawerKit.podspec b/DrawerKit.podspec index c134b9f..0f4a776 100644 --- a/DrawerKit.podspec +++ b/DrawerKit.podspec @@ -14,7 +14,7 @@ coming soon. s.homepage = "https://github.com/Babylonpartners/DrawerKit/blob/master/README.md" s.license = { :type => "MIT", :file => "LICENSE" } s.author = { "Wagner Truppel" => "wagner.truppel@babylonhealth.com" } - s.platform = :ios, "10.3" + s.platform = :ios, "10.0" s.source = { :git => "github.com/Babylonpartners/DrawerKit.git", :tag => "#{s.version}" } s.source_files = "Classes/**/*.{swift}" s.exclude_files = "Classes/Exclude" From 9b4ba38ba571b0e0291e8de4b6c40a791328800a Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Mon, 16 Oct 2017 17:03:07 +0100 Subject: [PATCH 76/85] Removed hidden extension in the LICENSE file. --- LICENSE.md => LICENSE | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename LICENSE.md => LICENSE (100%) diff --git a/LICENSE.md b/LICENSE similarity index 100% rename from LICENSE.md rename to LICENSE From ed3decd5ce5f3aae382bdd48120feb568da6ab6d Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Mon, 16 Oct 2017 17:06:08 +0100 Subject: [PATCH 77/85] Fixed source_files entry in the podspec. Removed the exclude_files entry. --- DrawerKit.podspec | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/DrawerKit.podspec b/DrawerKit.podspec index 0f4a776..edd9f1a 100644 --- a/DrawerKit.podspec +++ b/DrawerKit.podspec @@ -16,7 +16,6 @@ coming soon. s.author = { "Wagner Truppel" => "wagner.truppel@babylonhealth.com" } s.platform = :ios, "10.0" s.source = { :git => "github.com/Babylonpartners/DrawerKit.git", :tag => "#{s.version}" } - s.source_files = "Classes/**/*.{swift}" - s.exclude_files = "Classes/Exclude" + s.source_files = "DrawerKit/**/*.{swift}" end From 7fb403470eb5957fbdb1d51bfa039e40664ab68e Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Mon, 16 Oct 2017 17:18:38 +0100 Subject: [PATCH 78/85] Added a .swift-version file with the appropriate version of Swift to pod lib lint the pod. --- .swift-version | 1 + 1 file changed, 1 insertion(+) create mode 100644 .swift-version diff --git a/.swift-version b/.swift-version new file mode 100644 index 0000000..5186d07 --- /dev/null +++ b/.swift-version @@ -0,0 +1 @@ +4.0 From d9d81f550952a2e893f08f867bab53070a2f8282 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Mon, 16 Oct 2017 17:19:12 +0100 Subject: [PATCH 79/85] Fixed the homepage in the podspec. --- DrawerKit.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DrawerKit.podspec b/DrawerKit.podspec index edd9f1a..dae4848 100644 --- a/DrawerKit.podspec +++ b/DrawerKit.podspec @@ -11,7 +11,7 @@ in the map when using the Apple Maps app. The library is highly configurable, wi coming soon. DESC - s.homepage = "https://github.com/Babylonpartners/DrawerKit/blob/master/README.md" + s.homepage = "https://github.com/Babylonpartners/DrawerKit" s.license = { :type => "MIT", :file => "LICENSE" } s.author = { "Wagner Truppel" => "wagner.truppel@babylonhealth.com" } s.platform = :ios, "10.0" From 122463f914caa3de1f7cf9f2a090e79f903516cd Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Mon, 16 Oct 2017 18:59:17 +0100 Subject: [PATCH 80/85] Fixed a bug when running under iOS 10 in which the initial presentation animation doesn't complete, therefore not invoking viewDidAppear, which causes the drawer not to show up at all. The work-around means that the initial presentation and dismissal aren't interactive but Product signed off on that decision. The drawers work fine under iOS 11. --- .../DrawerDisplayController+Extensions.swift | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift index f5513d5..96f071f 100755 --- a/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift +++ b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift @@ -27,15 +27,33 @@ extension DrawerDisplayController: UIViewControllerTransitioningDelegate { } public func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { - guard isDrawerDraggable else { return nil } - return InteractionController(isPresentation: true, - presentingVC: presentingVC!, presentedVC: presentedVC) + if #available(iOS 11.0, *) { + guard isDrawerDraggable else { return nil } + return InteractionController(isPresentation: true, + presentingVC: presentingVC!, presentedVC: presentedVC) + } else { + // On iOS 10, there seems to be a bug in UIKit that causes the interactive animation + // not to complete under the conditions we have here, in which case viewDidAppear + // doesn't get called and the drawer isn't visible. An easy work-around is to return + // nil here, but that means the initial presentation and dismissal aren't going to + // be interactive. + return nil + } } public func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { - guard isDrawerDraggable else { return nil } - return InteractionController(isPresentation: true, - presentingVC: presentingVC!, presentedVC: presentedVC) + if #available(iOS 11.0, *) { + guard isDrawerDraggable else { return nil } + return InteractionController(isPresentation: false, + presentingVC: presentingVC!, presentedVC: presentedVC) + } else { + // On iOS 10, there seems to be a bug in UIKit that causes the interactive animation + // not to complete under the conditions we have here, in which case viewDidAppear + // doesn't get called and the drawer isn't visible. An easy work-around is to return + // nil here, but that means the initial presentation and dismissal aren't going to + // be interactive. + return nil + } } } From f1338bf914f1fd17a819dc10ccc73284a6948ccb Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Mon, 16 Oct 2017 19:09:03 +0100 Subject: [PATCH 81/85] Style change. --- .../Extensions/DrawerDisplayController+Extensions.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift index 96f071f..01e90ec 100755 --- a/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift +++ b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift @@ -30,7 +30,8 @@ extension DrawerDisplayController: UIViewControllerTransitioningDelegate { if #available(iOS 11.0, *) { guard isDrawerDraggable else { return nil } return InteractionController(isPresentation: true, - presentingVC: presentingVC!, presentedVC: presentedVC) + presentingVC: presentingVC!, + presentedVC: presentedVC) } else { // On iOS 10, there seems to be a bug in UIKit that causes the interactive animation // not to complete under the conditions we have here, in which case viewDidAppear @@ -45,7 +46,8 @@ extension DrawerDisplayController: UIViewControllerTransitioningDelegate { if #available(iOS 11.0, *) { guard isDrawerDraggable else { return nil } return InteractionController(isPresentation: false, - presentingVC: presentingVC!, presentedVC: presentedVC) + presentingVC: presentingVC!, + presentedVC: presentedVC) } else { // On iOS 10, there seems to be a bug in UIKit that causes the interactive animation // not to complete under the conditions we have here, in which case viewDidAppear From 12c594073863da8f695eebe8710a8ad3870f1f40 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Mon, 16 Oct 2017 19:11:35 +0100 Subject: [PATCH 82/85] Killed the use of IUO references. --- .../Extensions/DrawerDisplayController+Extensions.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift index 01e90ec..06e60b7 100755 --- a/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift +++ b/DrawerKit/DrawerKit/Internal API/Extensions/DrawerDisplayController+Extensions.swift @@ -28,9 +28,9 @@ extension DrawerDisplayController: UIViewControllerTransitioningDelegate { public func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { if #available(iOS 11.0, *) { - guard isDrawerDraggable else { return nil } + guard isDrawerDraggable, let presentingVC = presentingVC else { return nil } return InteractionController(isPresentation: true, - presentingVC: presentingVC!, + presentingVC: presentingVC, presentedVC: presentedVC) } else { // On iOS 10, there seems to be a bug in UIKit that causes the interactive animation @@ -44,9 +44,9 @@ extension DrawerDisplayController: UIViewControllerTransitioningDelegate { public func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { if #available(iOS 11.0, *) { - guard isDrawerDraggable else { return nil } + guard isDrawerDraggable, let presentingVC = presentingVC else { return nil } return InteractionController(isPresentation: false, - presentingVC: presentingVC!, + presentingVC: presentingVC, presentedVC: presentedVC) } else { // On iOS 10, there seems to be a bug in UIKit that causes the interactive animation From df3708207c4d0776ef2b70923ab6930974c6763f Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Mon, 16 Oct 2017 19:45:54 +0100 Subject: [PATCH 83/85] Changed author email address in the podspec. --- DrawerKit.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DrawerKit.podspec b/DrawerKit.podspec index dae4848..5d5c391 100644 --- a/DrawerKit.podspec +++ b/DrawerKit.podspec @@ -13,7 +13,7 @@ coming soon. s.homepage = "https://github.com/Babylonpartners/DrawerKit" s.license = { :type => "MIT", :file => "LICENSE" } - s.author = { "Wagner Truppel" => "wagner.truppel@babylonhealth.com" } + s.author = { "Wagner Truppel" => "ios-developers@babylonhealth.com" } s.platform = :ios, "10.0" s.source = { :git => "github.com/Babylonpartners/DrawerKit.git", :tag => "#{s.version}" } s.source_files = "DrawerKit/**/*.{swift}" From 21e39d4f9cf85898ad44a07a81c9d9b852605296 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Tue, 17 Oct 2017 10:37:25 +0100 Subject: [PATCH 84/85] Added support for Carthage. --- .../xcshareddata/xcschemes/DrawerKit.xcscheme | 82 +++++++++++++++++++ README.md | 14 ++++ 2 files changed, 96 insertions(+) create mode 100644 DrawerKit/DrawerKit.xcodeproj/xcshareddata/xcschemes/DrawerKit.xcscheme diff --git a/DrawerKit/DrawerKit.xcodeproj/xcshareddata/xcschemes/DrawerKit.xcscheme b/DrawerKit/DrawerKit.xcodeproj/xcshareddata/xcschemes/DrawerKit.xcscheme new file mode 100644 index 0000000..6d246e3 --- /dev/null +++ b/DrawerKit/DrawerKit.xcodeproj/xcshareddata/xcschemes/DrawerKit.xcscheme @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index 15e280b..30d938d 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@
--> +[![Carthage](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Version](https://img.shields.io/cocoapods/v/DrawerKit.svg?style=flat)](http://cocoapods.org/pods/DrawerKit) [![Platform](https://img.shields.io/cocoapods/p/DrawerKit.svg?style=flat)](http://cocoapods.org/pods/DrawerKit) [![Swift 4.0.x](https://img.shields.io/badge/Swift-4.0.x-orange.svg)](https://swift.org) @@ -212,6 +213,19 @@ collapsed (dismissed) is summarised by the pseudo-code below: collapse all the way (ie, dismiss) ``` +#### Carthage + +If you use [Carthage][] to manage your dependencies, simply add +DrawerKit to your `Cartfile`: + +``` +github "DrawerKit/DrawerKit" ~> 0.0.1 +``` + +If you use Carthage to build your dependencies, make sure you have added `DrawerKit.framework` +to the "_Linked Frameworks and Libraries_" section of your target, and have included them in +your Carthage framework copying build phase. + #### CocoaPods If you use [CocoaPods][] to manage your dependencies, simply add DrawerKit to your `Podfile`: From b1a2f1ae55f923efce21bf4ef86d2e43af849290 Mon Sep 17 00:00:00 2001 From: Wagner Truppel Date: Tue, 17 Oct 2017 10:52:11 +0100 Subject: [PATCH 85/85] [ci skip] Removed a commented out section from the README. Also, an excuse to turn CI off since DrawerKit fails it for lack of configuration for testing. --- README.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/README.md b/README.md index 30d938d..9253c51 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,5 @@ # DrawerKit - - [![Carthage](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Version](https://img.shields.io/cocoapods/v/DrawerKit.svg?style=flat)](http://cocoapods.org/pods/DrawerKit) [![Platform](https://img.shields.io/cocoapods/p/DrawerKit.svg?style=flat)](http://cocoapods.org/pods/DrawerKit)