diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Examples/FloatingPanelTableView/FloatingPanelTableView.xcodeproj/project.pbxproj b/Examples/FloatingPanelTableView/FloatingPanelTableView.xcodeproj/project.pbxproj new file mode 100644 index 00000000..c789c51c --- /dev/null +++ b/Examples/FloatingPanelTableView/FloatingPanelTableView.xcodeproj/project.pbxproj @@ -0,0 +1,407 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 58631EA6242B0976006AA07C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58631EA5242B0976006AA07C /* AppDelegate.swift */; }; + 58631EA8242B0976006AA07C /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58631EA7242B0976006AA07C /* SceneDelegate.swift */; }; + 58631EAA242B0976006AA07C /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58631EA9242B0976006AA07C /* ViewController.swift */; }; + 58631EAD242B0976006AA07C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 58631EAB242B0976006AA07C /* Main.storyboard */; }; + 58631EAF242B097C006AA07C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 58631EAE242B097C006AA07C /* Assets.xcassets */; }; + 58631EB2242B097C006AA07C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 58631EB0242B097C006AA07C /* LaunchScreen.storyboard */; }; + 58631EBA242BEED4006AA07C /* MapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58631EB9242BEED4006AA07C /* MapViewController.swift */; }; + 58DD5AFE2433D9F300DE8444 /* ViewController2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58DD5AFD2433D9F300DE8444 /* ViewController2.swift */; }; + 58DD5AFF2433ED4200DE8444 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58631EBC242BF2B4006AA07C /* FloatingPanel.framework */; }; + 58DD5B002433ED4200DE8444 /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 58631EBC242BF2B4006AA07C /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 58DD5B0724344DFF00DE8444 /* RCSegmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58DD5B0424344DFF00DE8444 /* RCSegmentView.swift */; }; + 58DD5B0824344DFF00DE8444 /* RCSegmentPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58DD5B0524344DFF00DE8444 /* RCSegmentPageViewController.swift */; }; + 58DD5B0924344DFF00DE8444 /* RCSegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58DD5B0624344DFF00DE8444 /* RCSegmentedControl.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 58DD5B012433ED4200DE8444 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 58DD5B002433ED4200DE8444 /* FloatingPanel.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 58631EA2242B0976006AA07C /* FloatingPanelTableView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FloatingPanelTableView.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 58631EA5242B0976006AA07C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 58631EA7242B0976006AA07C /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 58631EA9242B0976006AA07C /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 58631EAC242B0976006AA07C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 58631EAE242B097C006AA07C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 58631EB1242B097C006AA07C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 58631EB3242B097C006AA07C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 58631EB9242BEED4006AA07C /* MapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewController.swift; sourceTree = ""; }; + 58631EBC242BF2B4006AA07C /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 58DD5AFD2433D9F300DE8444 /* ViewController2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController2.swift; sourceTree = ""; }; + 58DD5B0424344DFF00DE8444 /* RCSegmentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RCSegmentView.swift; sourceTree = ""; }; + 58DD5B0524344DFF00DE8444 /* RCSegmentPageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RCSegmentPageViewController.swift; sourceTree = ""; }; + 58DD5B0624344DFF00DE8444 /* RCSegmentedControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RCSegmentedControl.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 58631E9F242B0976006AA07C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 58DD5AFF2433ED4200DE8444 /* FloatingPanel.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 58631E99242B0976006AA07C = { + isa = PBXGroup; + children = ( + 58631EA4242B0976006AA07C /* FloatingPanelTableView */, + 58631EA3242B0976006AA07C /* Products */, + 58631EBB242BF2B4006AA07C /* Frameworks */, + ); + sourceTree = ""; + }; + 58631EA3242B0976006AA07C /* Products */ = { + isa = PBXGroup; + children = ( + 58631EA2242B0976006AA07C /* FloatingPanelTableView.app */, + ); + name = Products; + sourceTree = ""; + }; + 58631EA4242B0976006AA07C /* FloatingPanelTableView */ = { + isa = PBXGroup; + children = ( + 58DD5B0224344DFF00DE8444 /* RCSegmentView */, + 58631EA5242B0976006AA07C /* AppDelegate.swift */, + 58631EA7242B0976006AA07C /* SceneDelegate.swift */, + 58631EA9242B0976006AA07C /* ViewController.swift */, + 58DD5AFD2433D9F300DE8444 /* ViewController2.swift */, + 58631EAB242B0976006AA07C /* Main.storyboard */, + 58631EAE242B097C006AA07C /* Assets.xcassets */, + 58631EB0242B097C006AA07C /* LaunchScreen.storyboard */, + 58631EB3242B097C006AA07C /* Info.plist */, + 58631EB9242BEED4006AA07C /* MapViewController.swift */, + ); + path = FloatingPanelTableView; + sourceTree = ""; + }; + 58631EBB242BF2B4006AA07C /* Frameworks */ = { + isa = PBXGroup; + children = ( + 58631EBC242BF2B4006AA07C /* FloatingPanel.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 58DD5B0224344DFF00DE8444 /* RCSegmentView */ = { + isa = PBXGroup; + children = ( + 58DD5B0324344DFF00DE8444 /* Classes */, + ); + path = RCSegmentView; + sourceTree = ""; + }; + 58DD5B0324344DFF00DE8444 /* Classes */ = { + isa = PBXGroup; + children = ( + 58DD5B0424344DFF00DE8444 /* RCSegmentView.swift */, + 58DD5B0524344DFF00DE8444 /* RCSegmentPageViewController.swift */, + 58DD5B0624344DFF00DE8444 /* RCSegmentedControl.swift */, + ); + path = Classes; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 58631EA1242B0976006AA07C /* FloatingPanelTableView */ = { + isa = PBXNativeTarget; + buildConfigurationList = 58631EB6242B097C006AA07C /* Build configuration list for PBXNativeTarget "FloatingPanelTableView" */; + buildPhases = ( + 58631E9E242B0976006AA07C /* Sources */, + 58631E9F242B0976006AA07C /* Frameworks */, + 58631EA0242B0976006AA07C /* Resources */, + 58DD5B012433ED4200DE8444 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = FloatingPanelTableView; + productName = FloatingPanelTableView; + productReference = 58631EA2242B0976006AA07C /* FloatingPanelTableView.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 58631E9A242B0976006AA07C /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1130; + LastUpgradeCheck = 1130; + ORGANIZATIONNAME = "Ramesh R C"; + TargetAttributes = { + 58631EA1242B0976006AA07C = { + CreatedOnToolsVersion = 11.3.1; + }; + }; + }; + buildConfigurationList = 58631E9D242B0976006AA07C /* Build configuration list for PBXProject "FloatingPanelTableView" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 58631E99242B0976006AA07C; + productRefGroup = 58631EA3242B0976006AA07C /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 58631EA1242B0976006AA07C /* FloatingPanelTableView */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 58631EA0242B0976006AA07C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 58631EB2242B097C006AA07C /* LaunchScreen.storyboard in Resources */, + 58631EAF242B097C006AA07C /* Assets.xcassets in Resources */, + 58631EAD242B0976006AA07C /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 58631E9E242B0976006AA07C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 58DD5AFE2433D9F300DE8444 /* ViewController2.swift in Sources */, + 58631EAA242B0976006AA07C /* ViewController.swift in Sources */, + 58631EA6242B0976006AA07C /* AppDelegate.swift in Sources */, + 58631EA8242B0976006AA07C /* SceneDelegate.swift in Sources */, + 58631EBA242BEED4006AA07C /* MapViewController.swift in Sources */, + 58DD5B0924344DFF00DE8444 /* RCSegmentedControl.swift in Sources */, + 58DD5B0724344DFF00DE8444 /* RCSegmentView.swift in Sources */, + 58DD5B0824344DFF00DE8444 /* RCSegmentPageViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 58631EAB242B0976006AA07C /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 58631EAC242B0976006AA07C /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 58631EB0242B097C006AA07C /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 58631EB1242B097C006AA07C /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 58631EB4242B097C006AA07C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 58631EB5242B097C006AA07C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 58631EB7242B097C006AA07C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = XB4UMY888T; + INFOPLIST_FILE = FloatingPanelTableView/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.andcharge.FloatingPanelTableView; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 58631EB8242B097C006AA07C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = XB4UMY888T; + INFOPLIST_FILE = FloatingPanelTableView/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.andcharge.FloatingPanelTableView; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 58631E9D242B0976006AA07C /* Build configuration list for PBXProject "FloatingPanelTableView" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 58631EB4242B097C006AA07C /* Debug */, + 58631EB5242B097C006AA07C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 58631EB6242B097C006AA07C /* Build configuration list for PBXNativeTarget "FloatingPanelTableView" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 58631EB7242B097C006AA07C /* Debug */, + 58631EB8242B097C006AA07C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 58631E9A242B0976006AA07C /* Project object */; +} diff --git a/Examples/FloatingPanelTableView/FloatingPanelTableView/AppDelegate.swift b/Examples/FloatingPanelTableView/FloatingPanelTableView/AppDelegate.swift new file mode 100644 index 00000000..4ef7672d --- /dev/null +++ b/Examples/FloatingPanelTableView/FloatingPanelTableView/AppDelegate.swift @@ -0,0 +1,37 @@ +// +// AppDelegate.swift +// FloatingPanelTableView +// +// Created by Ramesh R C on 25.03.20. +// Copyright © 2020 Ramesh R C. All rights reserved. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } + + +} + diff --git a/Examples/FloatingPanelTableView/FloatingPanelTableView/Assets.xcassets/AppIcon.appiconset/Contents.json b/Examples/FloatingPanelTableView/FloatingPanelTableView/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d8db8d65 --- /dev/null +++ b/Examples/FloatingPanelTableView/FloatingPanelTableView/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Examples/FloatingPanelTableView/FloatingPanelTableView/Assets.xcassets/Contents.json b/Examples/FloatingPanelTableView/FloatingPanelTableView/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/Examples/FloatingPanelTableView/FloatingPanelTableView/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/FloatingPanelTableView/FloatingPanelTableView/Assets.xcassets/Floating.imageset/Contents.json b/Examples/FloatingPanelTableView/FloatingPanelTableView/Assets.xcassets/Floating.imageset/Contents.json new file mode 100644 index 00000000..3b0752b7 --- /dev/null +++ b/Examples/FloatingPanelTableView/FloatingPanelTableView/Assets.xcassets/Floating.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Floating.jpg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/FloatingPanelTableView/FloatingPanelTableView/Assets.xcassets/Floating.imageset/Floating.jpg b/Examples/FloatingPanelTableView/FloatingPanelTableView/Assets.xcassets/Floating.imageset/Floating.jpg new file mode 100644 index 00000000..a53b95ed Binary files /dev/null and b/Examples/FloatingPanelTableView/FloatingPanelTableView/Assets.xcassets/Floating.imageset/Floating.jpg differ diff --git a/Examples/FloatingPanelTableView/FloatingPanelTableView/Base.lproj/LaunchScreen.storyboard b/Examples/FloatingPanelTableView/FloatingPanelTableView/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..865e9329 --- /dev/null +++ b/Examples/FloatingPanelTableView/FloatingPanelTableView/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/FloatingPanelTableView/FloatingPanelTableView/Base.lproj/Main.storyboard b/Examples/FloatingPanelTableView/FloatingPanelTableView/Base.lproj/Main.storyboard new file mode 100644 index 00000000..2d990ffb --- /dev/null +++ b/Examples/FloatingPanelTableView/FloatingPanelTableView/Base.lproj/Main.storyboard @@ -0,0 +1,204 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/FloatingPanelTableView/FloatingPanelTableView/Info.plist b/Examples/FloatingPanelTableView/FloatingPanelTableView/Info.plist new file mode 100644 index 00000000..2a3483c0 --- /dev/null +++ b/Examples/FloatingPanelTableView/FloatingPanelTableView/Info.plist @@ -0,0 +1,64 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Examples/FloatingPanelTableView/FloatingPanelTableView/MapViewController.swift b/Examples/FloatingPanelTableView/FloatingPanelTableView/MapViewController.swift new file mode 100644 index 00000000..d151c91e --- /dev/null +++ b/Examples/FloatingPanelTableView/FloatingPanelTableView/MapViewController.swift @@ -0,0 +1,221 @@ +// +// MapViewController.swift +// FloatingPanelTableView +// +// Created by Ramesh R C on 26.03.20. +// Copyright © 2020 Ramesh R C. All rights reserved. +// +import UIKit +import FloatingPanel +import MapKit + +class MapViewController: UIViewController { + + var fpc: FloatingPanelController! + var galleryDetailsVC: ViewController2! + let ppp = FloatingPanelHotelBehavior() + @IBOutlet weak var mapView: MKMapView! + var xpostion: CGFloat = 0.0 + var isLock:Bool = false + var panelHotelLayout:FloatingPanelHotelLayout? + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + // Initialize FloatingPanelController + fpc = FloatingPanelController() + fpc.delegate = self + + // Initialize FloatingPanelController and add the view + fpc.surfaceView.backgroundColor = .clear + fpc.surfaceView.cornerRadius = 0.0 + fpc.surfaceView.shadowHidden = true + fpc.surfaceView.borderWidth = 1.0 / traitCollection.displayScale + fpc.surfaceView.borderColor = UIColor.black.withAlphaComponent(0.2) + fpc.surfaceView.clipsToBounds = true + fpc.view.clipsToBounds = true + fpc.surfaceView.grabberHandle.isHidden = true + galleryDetailsVC = storyboard?.instantiateViewController(withIdentifier: "ViewController2") as? ViewController2 + + // Set a content view controller + fpc.set(contentViewController: galleryDetailsVC) + fpc.addPanel(toParent: self, belowView: nil, animated: false) +// DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: { +// self.fpc.track(scrollView: self.galleryDetailsVC.tableView) +// }) + + NotificationCenter.default.removeObserver(self, name: .FloatingTrackScrollView, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(setTrackScrollView), + name: .FloatingTrackScrollView, object: nil) + + self.moveAnimation() + galleryDetailsVC.hitView.mapView = mapView + } + override func viewDidAppear(_ animated: Bool) { + + } + override func viewDidDisappear(_ animated: Bool) { + + } + + @objc private func setTrackScrollView(_ notification: NSNotification){ + guard let trackView = notification.object as? UITableView else { + return + } + fpc.track(scrollView: nil) + fpc.track(scrollView: trackView) + } + + func moveAnimation(){ + let a = UIScreen.main.bounds.height - 200 + let b = max(0, fpc.surfaceView.frame.minY / a) + let hRedView = CGFloat(350) // Get from object + let trans = b * hRedView + debugPrint("trans",trans) + debugPrint("b:",b) + galleryDetailsVC.redView.transform = CGAffineTransform(translationX: 0, y: trans) + } +} + +extension MapViewController : FloatingPanelControllerDelegate{ + func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? { + panelHotelLayout = FloatingPanelHotelLayout(middle: self.view.frame.height/2, topBuffer: 200) + return panelHotelLayout + } + + func floatingPanel(_ vc: FloatingPanelController, behaviorFor newCollection: UITraitCollection) -> FloatingPanelBehavior? { + return FloatingPanelHotelBehavior() + } + func floatingPanelDidEndDecelerating(_ vc: FloatingPanelController) { + self.animate(vc) + } + func floatingPanelWillBeginDragging(_ vc: FloatingPanelController) { + debugPrint("floatingPanelWillBeginDragging") + } + func floatingPanelDidMove(_ vc: FloatingPanelController) { + self.animate(vc) + } + func floatingPanelDidViewLayout(_ vc: FloatingPanelController) { + self.animate(vc) + } + + func animate(_ vc: FloatingPanelController){ + let a = UIScreen.main.bounds.height - (UIScreen.main.bounds.height / 2 ) + let b = max(0, vc.surfaceView.frame.minY / a) + let b1 = vc.surfaceView.frame.minY / a + if(b1 < 0){ + galleryDetailsVC.viewWhiteAlpha.alpha = (abs(b1)) + }else{ + galleryDetailsVC.viewWhiteAlpha.alpha = 0 + } + let hRedView = CGFloat(350/2) // Get from object + let trans = b * hRedView + galleryDetailsVC.redView.transform = CGAffineTransform(translationX: 0, y: trans) + } +} + + +class FloatingPanelHotelLayout: FloatingPanelLayout { + var initialTranslationY: CGFloat = 0 + var positionReference: FloatingPanelLayoutReference { + return .fromSuperview + } + var initialPosition: FloatingPanelPosition { + return .tip + } + + var topInteractionBuffer: CGFloat { return self.topBuffer } + var bottomInteractionBuffer: CGFloat { return 0.0 } + var mm:CGFloat = 262.0 + var topBuffer:CGFloat = 314 + + init(middle:CGFloat,topBuffer:CGFloat) { + self.mm = middle + self.topBuffer = 350 //topBuffer + } + func insetFor(position: FloatingPanelPosition) -> CGFloat? { + switch position { + case .full: return -self.topBuffer + case .half: return UIScreen.main.bounds.height + case .tip: return UIScreen.main.bounds.height / 2 // Visible + ToolView + default: return nil + } + } + func backdropAlphaFor(position: FloatingPanelPosition) -> CGFloat { + return 0.0 + } +} + + +class FloatingPanelHotelBehavior: FloatingPanelBehavior { + var velocityThreshold: CGFloat { + return 15.0 + } + + func interactionAnimator(_ fpc: FloatingPanelController, to targetPosition: FloatingPanelPosition, with velocity: CGVector) -> UIViewPropertyAnimator { + let timing = timeingCurve(to: targetPosition, with: velocity) + return UIViewPropertyAnimator(duration: 0, timingParameters: timing) + } + + private func timeingCurve(to: FloatingPanelPosition, with velocity: CGVector) -> UITimingCurveProvider { + let damping = self.damping(with: velocity) + return UISpringTimingParameters(dampingRatio: damping, + frequencyResponse: 0.4, + initialVelocity: velocity) + } + + private func damping(with velocity: CGVector) -> CGFloat { + switch velocity.dy { + case ...(-velocityThreshold): + return 0.7 + case velocityThreshold...: + return 0.7 + default: + return 1.0 + } + } +} + + + + +protocol LayoutGuideProvider { + var topAnchor: NSLayoutYAxisAnchor { get } + var bottomAnchor: NSLayoutYAxisAnchor { get } +} +extension UILayoutGuide: LayoutGuideProvider {} + +class CustomLayoutGuide: LayoutGuideProvider { + let topAnchor: NSLayoutYAxisAnchor + let bottomAnchor: NSLayoutYAxisAnchor + init(topAnchor: NSLayoutYAxisAnchor, bottomAnchor: NSLayoutYAxisAnchor) { + self.topAnchor = topAnchor + self.bottomAnchor = bottomAnchor + } +} + +extension MapViewController { + var layoutInsets: UIEdgeInsets { + if #available(iOS 11.0, *) { + return view.safeAreaInsets + } else { + return UIEdgeInsets(top: topLayoutGuide.length, + left: 0.0, + bottom: bottomLayoutGuide.length, + right: 0.0) + } + } + + var layoutGuide: LayoutGuideProvider { + if #available(iOS 11.0, *) { + return view!.safeAreaLayoutGuide + } else { + return CustomLayoutGuide(topAnchor: topLayoutGuide.bottomAnchor, + bottomAnchor: bottomLayoutGuide.topAnchor) + } + } +} + +extension Notification.Name { + static let FloatingTrackScrollView = Notification.Name("FloatingTrackScrollView") +} diff --git a/Examples/FloatingPanelTableView/FloatingPanelTableView/RCSegmentView/Classes/RCSegmentPageViewController.swift b/Examples/FloatingPanelTableView/FloatingPanelTableView/RCSegmentView/Classes/RCSegmentPageViewController.swift new file mode 100644 index 00000000..2f6d45bb --- /dev/null +++ b/Examples/FloatingPanelTableView/FloatingPanelTableView/RCSegmentView/Classes/RCSegmentPageViewController.swift @@ -0,0 +1,125 @@ +// +// RCSegmentPageViewController.swift +// Example +// +// Created by Ramesh R C on 21.03.20. +// Copyright © 2020 Ramesh R C. All rights reserved. +// + +import UIKit + +protocol RCSegmentPageViewControllerDelegate:class { + func didDisplayViewController(vc:UIViewController, at index:Int) +} + +class RCSegmentPageViewController: UIPageViewController { + + weak var segmentPageDelegate:RCSegmentPageViewControllerDelegate? + public var orderedViewController : [UIViewController] = [] { + didSet { + DispatchQueue.main.async { + self.refreshViewControllers() + } + } + } + fileprivate lazy var currentIndex = 0 + + override init(transitionStyle style: UIPageViewController.TransitionStyle, navigationOrientation: UIPageViewController.NavigationOrientation, options: [UIPageViewController.OptionsKey : Any]? = nil) { + super.init(transitionStyle: .scroll, navigationOrientation: .horizontal, options: options) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + self.view.backgroundColor = UIColor.clear + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + self.prepareView() + } + + private func prepareView(){ + self.dataSource = self + self.delegate = self + } + + private func refreshViewControllers(){ + if let firstVC = orderedViewController.first { + setViewControllers([firstVC], direction: .forward, animated: true, completion: nil) + } + } + + private func getVcIndex(vc:UIViewController) -> Int?{ + if(orderedViewController.count > 0 && orderedViewController.firstIndex(of: vc) ?? -1 < orderedViewController.count){ + return orderedViewController.firstIndex(of: vc) + } + return nil + } + + func visibleVC(at index:Int){ + guard orderedViewController.count > index, + currentIndex != index else { + return + } + let vc = orderedViewController[index] + // Managing scroll direction + if (currentIndex < index){ + setViewControllers([vc], direction:UIPageViewController.NavigationDirection.forward, animated: true, completion: { _ in}) + }else{ + setViewControllers([vc], direction:UIPageViewController.NavigationDirection.reverse, animated: true, completion: { _ in}) + } + currentIndex = index + } + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + } + */ + +} + +extension RCSegmentPageViewController: UIPageViewControllerDataSource, UIPageViewControllerDelegate { + + func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { + guard let viewControllerIndex = orderedViewController.firstIndex(of: viewController) else { return nil } + let previousIndex = viewControllerIndex - 1 + guard previousIndex >= 0 else {return nil } + guard orderedViewController.count > previousIndex else { return nil } + return orderedViewController[previousIndex] + } + + func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { + guard let viewControllerIndex = orderedViewController.firstIndex(of: viewController) else { return nil } + let nextIndex = viewControllerIndex + 1 + guard nextIndex < orderedViewController.count else { return nil } + guard orderedViewController.count > nextIndex else { return nil } + return orderedViewController[nextIndex] + } + + func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { + if(finished){ + guard let vc = pageViewController.viewControllers?.first else { return } + guard let d = segmentPageDelegate, let index = self.getVcIndex(vc: vc) else {return} + d.didDisplayViewController(vc: vc, at: index) + self.currentIndex = index + } + } + + func presentationCount(for: UIPageViewController) -> Int { + return orderedViewController.count - 1 + } + + func presentationIndex(for pageViewController: UIPageViewController) -> Int { + return -1 + } +} diff --git a/Examples/FloatingPanelTableView/FloatingPanelTableView/RCSegmentView/Classes/RCSegmentView.swift b/Examples/FloatingPanelTableView/FloatingPanelTableView/RCSegmentView/Classes/RCSegmentView.swift new file mode 100644 index 00000000..dd078f29 --- /dev/null +++ b/Examples/FloatingPanelTableView/FloatingPanelTableView/RCSegmentView/Classes/RCSegmentView.swift @@ -0,0 +1,132 @@ +// +// RCSegmentView.swift +// Example +// +// Created by Ramesh R C on 21.03.20. +// Copyright © 2020 Ramesh R C. All rights reserved. +// + +import UIKit + +public protocol RCSegmentViewDelegate:class { + func setController() -> [RCSegmentSlide] + func didDisplayViewController(vc:UIViewController, at index:Int) + func updateConfig() -> RCSegmentButtonConfig +} + +open class RCSegmentView: UIView { + + /* + // Only override draw() if you perform custom drawing. + // An empty implementation adversely affects performance during animation. + override func draw(_ rect: CGRect) { + // Drawing code + } + */ + public var delegate:RCSegmentViewDelegate? + public var config:RCSegmentButtonConfig = RCSegmentButtonConfig() + var segmentViewHeight:NSLayoutConstraint? + + fileprivate lazy var segmentControllerView : RCSegmentedControl = { + let segmentedControl = RCSegmentedControl() + segmentedControl.translatesAutoresizingMaskIntoConstraints = false + segmentedControl.delegate = self + return segmentedControl + }() + + fileprivate lazy var segmentPageViewController : RCSegmentPageViewController = { + let sPageView = RCSegmentPageViewController() + sPageView.view.translatesAutoresizingMaskIntoConstraints = false + sPageView.segmentPageDelegate = self + return sPageView + }() + + convenience override init(frame:CGRect) { + self.init(frame: frame) + } + + convenience init(frame:CGRect,withConfig config:RCSegmentButtonConfig) { + self.init(frame: UIScreen.main.bounds) + self.config = config + } + override open func draw(_ rect: CGRect) { + super.draw(rect) + self.backgroundColor = UIColor.white + updateView() + } + + private func updateView() { + self.addSegmentTabView() + self.addSegmentPageViewController() + + self.setController() + } + + private func setController(){ + guard let d = delegate else {return} + segmentPageViewController.orderedViewController = d.setController().map({$0.vc}) + segmentControllerView.config = d.updateConfig() + segmentControllerView.setButtonTitles(buttonTitles: d.setController().map({$0.buttonTitle})) + if (d.setController().map({$0.buttonTitle}).count < 2){ + segmentViewHeight?.constant = 0 + self.layoutIfNeeded() + } + } + + + func addSegmentTabView(){ + self.addSubview(segmentControllerView) + segmentControllerView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true + segmentControllerView.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true + segmentControllerView.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true + segmentViewHeight = segmentControllerView.heightAnchor.constraint(equalToConstant: 40) + segmentViewHeight?.isActive = true + } + + func addSegmentPageViewController(){ + self.addSubview(segmentPageViewController.view) + segmentPageViewController.view.topAnchor.constraint(equalTo: segmentControllerView.bottomAnchor, constant: 10).isActive = true +// segmentPageViewController.view.topAnchor.constraint(equalTo: segmentControllerView.bottomAnchor).isActive = true + segmentPageViewController.view.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true + segmentPageViewController.view.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true + segmentPageViewController.view.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true + } +} + +extension RCSegmentView: RCSegmentPageViewControllerDelegate { + + func didDisplayViewController(vc: UIViewController, at index: Int) { + guard let d = delegate else {return} + d.didDisplayViewController(vc: vc, at: index) + segmentControllerView.setIndex(index: index) + } +} +extension RCSegmentView: RCSegmentedControlDelegate { + func changeToIndex(index: Int) { + // Heree updateing screen + segmentPageViewController.visibleVC(at: index) + } +} + +public struct RCSegmentSlide { + public var buttonTitle : String + public var vc : UIViewController + + public init(buttonTitle: String, vc: UIViewController) { + self.buttonTitle = buttonTitle + self.vc = vc + } +} + +public struct RCSegmentButtonConfig { + var textColor:UIColor = .black + var selectorViewColor: UIColor = .red + var selectorTextColor: UIColor = .red + var bottomViewColor: UIColor = .gray + var titleFont: UIFont = UIFont.systemFont(ofSize: 15) + var titleSelectionFont: UIFont = UIFont.systemFont(ofSize: 16) + + public init() { + + } +} diff --git a/Examples/FloatingPanelTableView/FloatingPanelTableView/RCSegmentView/Classes/RCSegmentedControl.swift b/Examples/FloatingPanelTableView/FloatingPanelTableView/RCSegmentView/Classes/RCSegmentedControl.swift new file mode 100644 index 00000000..d2ed7c3d --- /dev/null +++ b/Examples/FloatingPanelTableView/FloatingPanelTableView/RCSegmentView/Classes/RCSegmentedControl.swift @@ -0,0 +1,183 @@ +// +// RCSegmentedControl.swift +// +// +// Created by Ramesh R C on 17.10.19. +// Copyright © 2019 SyroCon. All rights reserved. +// + +import Foundation +import UIKit +protocol RCSegmentedControlDelegate:class { + func changeToIndex(index:Int) +} + +class RCSegmentedControl: UIView { + private var buttonTitles:[String]! + private var buttons: [UIButton]! + private var selectorView: UIView! + private var bottomView: UIView! + +// var textColor:UIColor = .black +// var selectorViewColor: UIColor = .red +// var selectorTextColor: UIColor = .red +// var bottomViewColor: UIColor = .gray +// var titleFont: UIFont = UIFont.systemFont(ofSize: 15) +// var titleSelectionFont: UIFont = UIFont.systemFont(ofSize: 16) + + weak var delegate:RCSegmentedControlDelegate? + var config:RCSegmentButtonConfig = RCSegmentButtonConfig() + var selectorViewLeft:NSLayoutConstraint? + + private var _selectedIndex:Int = 0 + public var seletedIndex : Int { + return _selectedIndex + } + + convenience init(frame:CGRect,buttonTitle:[String]) { + self.init(frame: frame) + self.buttonTitles = buttonTitle + } + + override func draw(_ rect: CGRect) { + super.draw(rect) + self.backgroundColor = UIColor.white + updateView() + } + override func layoutSubviews() { + configSelectorView() + } + func setButtonTitles(buttonTitles:[String]) { + self.buttonTitles = buttonTitles + self.updateView() + } + + func setIndex(index:Int) { + buttons.forEach({ $0.setTitleColor(config.textColor, for: .normal) }) + let button = buttons[index] + self.buttonAction(sender: button) + +// _selectedIndex = index +// button.setTitleColor(config.selectorTextColor, for: .normal) +// let selectorPosition = frame.width/CGFloat(buttonTitles.count) * CGFloat(index) +// UIView.animate(withDuration: 0.2) { +//// self.selectorView.frame.origin.x = selectorPosition +// self.selectorViewLeft?.constant = selectorPosition +// self.layoutIfNeeded() +// } + } + + @objc func buttonAction(sender:UIButton) { + for (buttonIndex, btn) in buttons.enumerated() { + btn.setTitleColor(config.textColor, for: .normal) + btn.titleLabel?.font = config.titleFont + if btn == sender { + let selectorPosition = frame.width/CGFloat(buttonTitles.count) * CGFloat(buttonIndex) + _selectedIndex = buttonIndex + delegate?.changeToIndex(index: _selectedIndex) + UIView.animate(withDuration: 0.3) { +// self.selectorView.frame.origin.x = selectorPosition + self.selectorViewLeft?.constant = selectorPosition + self.layoutIfNeeded() + } + btn.setTitleColor(config.selectorTextColor, for: .normal) + btn.titleLabel?.font = config.titleSelectionFont + } + } + } +} + +//Configuration View +extension RCSegmentedControl { + private func updateView() { + createButton() + configBottomLineView() + configStackView() + } + + private func configStackView() { + let scrollview = UIScrollView() + scrollview.translatesAutoresizingMaskIntoConstraints = false + scrollview.showsVerticalScrollIndicator = false + scrollview.showsHorizontalScrollIndicator = false + addSubview(scrollview) + scrollview.topAnchor.constraint(equalTo: self.topAnchor).isActive = true + scrollview.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true + scrollview.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true + scrollview.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true + + let stack = UIStackView(arrangedSubviews: buttons) + stack.axis = .horizontal + stack.alignment = .fill + stack.distribution = .fillEqually + scrollview.addSubview(stack) +// addSubview(stack) + stack.translatesAutoresizingMaskIntoConstraints = false + stack.topAnchor.constraint(equalTo: scrollview.topAnchor).isActive = true + stack.bottomAnchor.constraint(equalTo: scrollview.bottomAnchor).isActive = true + stack.leftAnchor.constraint(equalTo: scrollview.leftAnchor).isActive = true + stack.rightAnchor.constraint(equalTo: scrollview.rightAnchor).isActive = true + + let wConstraint = stack.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 1) +// wConstraint.priority = .defaultLow + wConstraint.isActive = true + } + + private func configSelectorView() { + guard let buttonTitles = self.buttonTitles, + selectorView == nil else { + return + } + let selectorWidth = frame.width / CGFloat(buttonTitles.count) + let height = CGFloat(buttonTitles.count < 2 ? 0 : 2) + selectorView = UIView() + selectorView.translatesAutoresizingMaskIntoConstraints = false + selectorView.backgroundColor = config.selectorViewColor + insertSubview(selectorView, aboveSubview: self) + + selectorViewLeft = selectorView.leftAnchor.constraint(equalTo: bottomView.leftAnchor, constant: 0) + selectorViewLeft?.isActive = true + selectorView.widthAnchor.constraint(equalToConstant: (selectorWidth - 20)).isActive = true + selectorView.heightAnchor.constraint(equalToConstant: height).isActive = true + selectorView.topAnchor.constraint(equalTo: bottomView.topAnchor, constant: 0).isActive = true + } + + private func configBottomLineView() { + guard let buttonTitles = self.buttonTitles else {return} + + let height = CGFloat(buttonTitles.count < 2 ? 0 : 2) + bottomView = UIView() + bottomView.translatesAutoresizingMaskIntoConstraints = false + bottomView.backgroundColor = config.bottomViewColor + addSubview(bottomView) + bottomView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 10).isActive = true + bottomView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -10).isActive = true + bottomView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true + bottomView.heightAnchor.constraint(equalToConstant: height).isActive = true + } + + private func createButton() { + buttons = [UIButton]() + buttons.removeAll() + subviews.forEach({$0.removeFromSuperview()}) + guard let buttonTitles = self.buttonTitles else { + return + } + for buttonTitle in buttonTitles { + let button = UIButton(type: .custom) + button.setTitle(buttonTitle, for: .normal) + button.addTarget(self, action:#selector(RCSegmentedControl.buttonAction(sender:)), for: .touchUpInside) + button.setTitleColor(config.textColor, for: .normal) + buttons.append(button) + button.titleLabel?.font = config.titleFont + button.sizeToFit() + } + buttons[0].setTitleColor(config.selectorTextColor, for: .normal) + buttons[0].titleLabel?.font = config.titleSelectionFont + } + +} + +public struct RCSegmentedConfig { + +} diff --git a/Examples/FloatingPanelTableView/FloatingPanelTableView/SceneDelegate.swift b/Examples/FloatingPanelTableView/FloatingPanelTableView/SceneDelegate.swift new file mode 100644 index 00000000..6df6471d --- /dev/null +++ b/Examples/FloatingPanelTableView/FloatingPanelTableView/SceneDelegate.swift @@ -0,0 +1,53 @@ +// +// SceneDelegate.swift +// FloatingPanelTableView +// +// Created by Ramesh R C on 25.03.20. +// Copyright © 2020 Ramesh R C. All rights reserved. +// + +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + guard let _ = (scene as? UIWindowScene) else { return } + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } + + +} + diff --git a/Examples/FloatingPanelTableView/FloatingPanelTableView/ViewController.swift b/Examples/FloatingPanelTableView/FloatingPanelTableView/ViewController.swift new file mode 100644 index 00000000..d75e5183 --- /dev/null +++ b/Examples/FloatingPanelTableView/FloatingPanelTableView/ViewController.swift @@ -0,0 +1,72 @@ +// +// ViewController.swift +// FloatingPanelTableView +// +// Created by Ramesh R C on 25.03.20. +// Copyright © 2020 Ramesh R C. All rights reserved. +// + +import UIKit + +class ViewController: UIViewController { + @IBOutlet weak var tableView: UITableView!{ + didSet{ + tableView.dataSource = self + tableView.delegate = self + } + } + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + } +} + +extension ViewController: UITableViewDelegate, UITableViewDataSource { + func numberOfSections(in tableView: UITableView) -> Int { + 4 + } + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + 0 + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + return UITableViewCell() + } + + + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + let view = UIView() + view.backgroundColor = UIColor.random() + return view + } + + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + if section == 0{ + return (tableView.frame.height / 2) + }else if section == 1{ + return (tableView.frame.height / 4) + }else if section == 1{ + return (tableView.frame.height / 4) + } + else{ + return (tableView.frame.height) + } + } + +} + + +extension UIColor { + static func random() -> UIColor { + return UIColor(red: .random(), + green: .random(), + blue: .random(), + alpha: 1.0) + } +} + +extension CGFloat { + static func random() -> CGFloat { + return CGFloat(arc4random()) / CGFloat(UInt32.max) + } +} diff --git a/Examples/FloatingPanelTableView/FloatingPanelTableView/ViewController2.swift b/Examples/FloatingPanelTableView/FloatingPanelTableView/ViewController2.swift new file mode 100644 index 00000000..31f2c8cc --- /dev/null +++ b/Examples/FloatingPanelTableView/FloatingPanelTableView/ViewController2.swift @@ -0,0 +1,118 @@ +// +// ViewController2.swift +// FloatingPanelTableView +// +// Created by Ramesh R C on 31.03.20. +// Copyright © 2020 Ramesh R C. All rights reserved. +// + +import UIKit +import MapKit + +class ViewController2: UIViewController { + + @IBOutlet weak var redView: PictureAreaView! + @IBOutlet weak var hitView: HitView! + @IBOutlet weak var segementView: RCSegmentView!{ + didSet{ + segementView.delegate = self + } + } + @IBOutlet weak var viewWhiteAlpha: UIView!{ + didSet{ + viewWhiteAlpha.alpha = 0 + } + } + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + } + + + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + } + */ + +} +extension ViewController2 : RCSegmentViewDelegate{ + func setController() -> [RCSegmentSlide] { + guard let vc3 = storyboard?.instantiateViewController(withIdentifier: "ViewController3") as? ViewController3 else { return [] } + let segmentSlide = RCSegmentSlide(buttonTitle: "Sample", vc: vc3) + return [segmentSlide,segmentSlide] + } + + func didDisplayViewController(vc: UIViewController, at index: Int) { + // + } + + func updateConfig() -> RCSegmentButtonConfig { + var config = RCSegmentButtonConfig() + config.selectorViewColor = UIColor.blue + config.selectorTextColor = UIColor.white + config.bottomViewColor = UIColor.clear + return config + } + + +} + +class ViewController3: UIViewController { + + @IBOutlet weak var tableView: UITableView!{ + didSet{ + tableView.dataSource = self + tableView.delegate = self + } + } + + override func viewDidLoad() { + super.viewDidLoad() + + NotificationCenter.default.post(name: .FloatingTrackScrollView,object: self.tableView) + // Do any additional setup after loading the view. + } + + + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + } + */ + +} +extension ViewController3 : UITableViewDelegate, UITableViewDataSource,UIScrollViewDelegate{ + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return 50 + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = UITableViewCell(style: .default, reuseIdentifier: nil) + cell.textLabel?.text = "Aasdsfs fs asds" + return cell + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + debugPrint("scrollViewDidScroll") + } +} + + +class PictureAreaView:UIView{ +} +class HitView:UIView{ + weak var mapView: MKMapView! + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return mapView.hitTest(point, with: event) + } +} diff --git a/Examples/ImageGallery/ImageGallery.xcodeproj/project.pbxproj b/Examples/ImageGallery/ImageGallery.xcodeproj/project.pbxproj new file mode 100644 index 00000000..15849aa3 --- /dev/null +++ b/Examples/ImageGallery/ImageGallery.xcodeproj/project.pbxproj @@ -0,0 +1,379 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 5819799823AB8C6000381D2C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5819799723AB8C6000381D2C /* AppDelegate.swift */; }; + 5819799C23AB8C6000381D2C /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5819799B23AB8C6000381D2C /* ViewController.swift */; }; + 5819799F23AB8C6000381D2C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5819799D23AB8C6000381D2C /* Main.storyboard */; }; + 581979A123AB8C6200381D2C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 581979A023AB8C6200381D2C /* Assets.xcassets */; }; + 581979A423AB8C6200381D2C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 581979A223AB8C6200381D2C /* LaunchScreen.storyboard */; }; + 581979B123AB8DAD00381D2C /* GalleryDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581979B023AB8DAD00381D2C /* GalleryDetailsViewController.swift */; }; + 581979B323B655A800381D2C /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581979B223B655A700381D2C /* SceneDelegate.swift */; }; + 581979B423B658C500381D2C /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 581979AC23AB8C6F00381D2C /* FloatingPanel.framework */; }; + 581979B523B658C500381D2C /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 581979AC23AB8C6F00381D2C /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 581979B623B658C500381D2C /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 581979B523B658C500381D2C /* FloatingPanel.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 5819799423AB8C6000381D2C /* ImageGallery.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ImageGallery.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 5819799723AB8C6000381D2C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 5819799B23AB8C6000381D2C /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 5819799E23AB8C6000381D2C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 581979A023AB8C6200381D2C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 581979A323AB8C6200381D2C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 581979A523AB8C6200381D2C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 581979AC23AB8C6F00381D2C /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 581979B023AB8DAD00381D2C /* GalleryDetailsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GalleryDetailsViewController.swift; sourceTree = ""; }; + 581979B223B655A700381D2C /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 5819799123AB8C6000381D2C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 581979B423B658C500381D2C /* FloatingPanel.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 5819798B23AB8C6000381D2C = { + isa = PBXGroup; + children = ( + 5819799623AB8C6000381D2C /* ImageGallery */, + 5819799523AB8C6000381D2C /* Products */, + 581979AB23AB8C6F00381D2C /* Frameworks */, + ); + sourceTree = ""; + }; + 5819799523AB8C6000381D2C /* Products */ = { + isa = PBXGroup; + children = ( + 5819799423AB8C6000381D2C /* ImageGallery.app */, + ); + name = Products; + sourceTree = ""; + }; + 5819799623AB8C6000381D2C /* ImageGallery */ = { + isa = PBXGroup; + children = ( + 581979B223B655A700381D2C /* SceneDelegate.swift */, + 581979B023AB8DAD00381D2C /* GalleryDetailsViewController.swift */, + 5819799723AB8C6000381D2C /* AppDelegate.swift */, + 5819799B23AB8C6000381D2C /* ViewController.swift */, + 5819799D23AB8C6000381D2C /* Main.storyboard */, + 581979A023AB8C6200381D2C /* Assets.xcassets */, + 581979A223AB8C6200381D2C /* LaunchScreen.storyboard */, + 581979A523AB8C6200381D2C /* Info.plist */, + ); + path = ImageGallery; + sourceTree = ""; + }; + 581979AB23AB8C6F00381D2C /* Frameworks */ = { + isa = PBXGroup; + children = ( + 581979AC23AB8C6F00381D2C /* FloatingPanel.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 5819799323AB8C6000381D2C /* ImageGallery */ = { + isa = PBXNativeTarget; + buildConfigurationList = 581979A823AB8C6200381D2C /* Build configuration list for PBXNativeTarget "ImageGallery" */; + buildPhases = ( + 5819799023AB8C6000381D2C /* Sources */, + 5819799123AB8C6000381D2C /* Frameworks */, + 5819799223AB8C6000381D2C /* Resources */, + 581979B623B658C500381D2C /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ImageGallery; + productName = ImageGallery; + productReference = 5819799423AB8C6000381D2C /* ImageGallery.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 5819798C23AB8C6000381D2C /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1110; + LastUpgradeCheck = 1110; + ORGANIZATIONNAME = "Ramesh R C"; + TargetAttributes = { + 5819799323AB8C6000381D2C = { + CreatedOnToolsVersion = 11.1; + }; + }; + }; + buildConfigurationList = 5819798F23AB8C6000381D2C /* Build configuration list for PBXProject "ImageGallery" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 5819798B23AB8C6000381D2C; + productRefGroup = 5819799523AB8C6000381D2C /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 5819799323AB8C6000381D2C /* ImageGallery */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 5819799223AB8C6000381D2C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 581979A423AB8C6200381D2C /* LaunchScreen.storyboard in Resources */, + 581979A123AB8C6200381D2C /* Assets.xcassets in Resources */, + 5819799F23AB8C6000381D2C /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 5819799023AB8C6000381D2C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5819799C23AB8C6000381D2C /* ViewController.swift in Sources */, + 5819799823AB8C6000381D2C /* AppDelegate.swift in Sources */, + 581979B123AB8DAD00381D2C /* GalleryDetailsViewController.swift in Sources */, + 581979B323B655A800381D2C /* SceneDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 5819799D23AB8C6000381D2C /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 5819799E23AB8C6000381D2C /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 581979A223AB8C6200381D2C /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 581979A323AB8C6200381D2C /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 581979A623AB8C6200381D2C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 581979A723AB8C6200381D2C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 581979A923AB8C6200381D2C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = XB4UMY888T; + INFOPLIST_FILE = ImageGallery/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.andcharge.ImageGallery; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = "And Charge wildcard"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 581979AA23AB8C6200381D2C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = XB4UMY888T; + INFOPLIST_FILE = ImageGallery/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.andcharge.ImageGallery; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = "And Charge wildcard"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 5819798F23AB8C6000381D2C /* Build configuration list for PBXProject "ImageGallery" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 581979A623AB8C6200381D2C /* Debug */, + 581979A723AB8C6200381D2C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 581979A823AB8C6200381D2C /* Build configuration list for PBXNativeTarget "ImageGallery" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 581979A923AB8C6200381D2C /* Debug */, + 581979AA23AB8C6200381D2C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 5819798C23AB8C6000381D2C /* Project object */; +} diff --git a/Examples/ImageGallery/ImageGallery/AppDelegate.swift b/Examples/ImageGallery/ImageGallery/AppDelegate.swift new file mode 100644 index 00000000..0ff8a588 --- /dev/null +++ b/Examples/ImageGallery/ImageGallery/AppDelegate.swift @@ -0,0 +1,19 @@ +// +// AppDelegate.swift +// ImageGallery +// +// Created by Ramesh R C on 19.12.19. +// Copyright © 2019 Ramesh R C. All rights reserved. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + return true + } +} + diff --git a/Examples/ImageGallery/ImageGallery/Assets.xcassets/AppIcon.appiconset/Contents.json b/Examples/ImageGallery/ImageGallery/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d8db8d65 --- /dev/null +++ b/Examples/ImageGallery/ImageGallery/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Examples/ImageGallery/ImageGallery/Assets.xcassets/Contents.json b/Examples/ImageGallery/ImageGallery/Assets.xcassets/Contents.json new file mode 100644 index 00000000..da4a164c --- /dev/null +++ b/Examples/ImageGallery/ImageGallery/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Examples/ImageGallery/ImageGallery/Assets.xcassets/Gallery-1.imageset/Contents.json b/Examples/ImageGallery/ImageGallery/Assets.xcassets/Gallery-1.imageset/Contents.json new file mode 100644 index 00000000..eb6e3eff --- /dev/null +++ b/Examples/ImageGallery/ImageGallery/Assets.xcassets/Gallery-1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Gallery-1.jpeg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Examples/ImageGallery/ImageGallery/Assets.xcassets/Gallery-1.imageset/Gallery-1.jpeg b/Examples/ImageGallery/ImageGallery/Assets.xcassets/Gallery-1.imageset/Gallery-1.jpeg new file mode 100644 index 00000000..6b43cb3a Binary files /dev/null and b/Examples/ImageGallery/ImageGallery/Assets.xcassets/Gallery-1.imageset/Gallery-1.jpeg differ diff --git a/Examples/ImageGallery/ImageGallery/Base.lproj/LaunchScreen.storyboard b/Examples/ImageGallery/ImageGallery/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..865e9329 --- /dev/null +++ b/Examples/ImageGallery/ImageGallery/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/ImageGallery/ImageGallery/Base.lproj/Main.storyboard b/Examples/ImageGallery/ImageGallery/Base.lproj/Main.storyboard new file mode 100644 index 00000000..31a800ed --- /dev/null +++ b/Examples/ImageGallery/ImageGallery/Base.lproj/Main.storyboard @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/ImageGallery/ImageGallery/GalleryDetailsViewController.swift b/Examples/ImageGallery/ImageGallery/GalleryDetailsViewController.swift new file mode 100644 index 00000000..9b0d0d9c --- /dev/null +++ b/Examples/ImageGallery/ImageGallery/GalleryDetailsViewController.swift @@ -0,0 +1,328 @@ +// +// GalleryDetailsViewController.swift +// FloatingPanel +// +// Created by Ramesh R C on 19.12.19. +// Copyright © 2019 scenee. All rights reserved. +// + +import UIKit +import MapKit + +class GalleryDetailsViewController: UIViewController { + + @IBOutlet weak var viewContent: UITableView!{ + didSet{ +// viewContent.delegate = self + } + } + @IBOutlet weak var viewPicture: UIView! + @IBOutlet weak var viewNav: UIView!{ + didSet{ + viewNav.alpha = 0 + } + } + @IBOutlet weak var viewDrag: UIView! + private var initialTranslationY: CGFloat = 0 + @IBOutlet weak var topConstraint: NSLayoutConstraint! + @IBOutlet weak var hitView: HitView! + @IBOutlet weak var lblTitle: UILabel! + var gesture:UIPanGestureRecognizer? + var activeDrag:Bool = false{ + didSet{ + guard let gestureEvent = gesture, + let viewObj = viewDrag else{ + return + } + gesture?.isEnabled = activeDrag +// if(activeDrag){ +// viewObj.addGestureRecognizer(gestureEvent) +// }else{ +// viewObj.removeGestureRecognizer(gestureEvent) +// } + } + } + var shouldScroll:Bool?{ + didSet{ + viewContent.isScrollEnabled = shouldScroll ?? false +// viewContent.panGestureRecognizer.isEnabled = shouldScroll ?? false + } + } + + // @IBOutlet weak var scrollView: UIScrollView! + +// override func viewDidLayoutSubviews() { +// if (self.topConstraint.constant == 0){ +//// self.activeDrag = true +// self.shouldScroll = false +// self.activeDrag = false +// } +// +// if (self.topConstraint.constant == -200){ +//// self.activeDrag = true +// self.shouldScroll = true +// self.activeDrag = true +// } +// } + var setLockStatus: ((_ isLock: Bool) -> Void)? + + @IBOutlet var topViewImage: NSLayoutConstraint! + var topViewImageWindow: NSLayoutConstraint! + + override func viewDidLoad() { + super.viewDidLoad() + + viewPicture.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner] + viewPicture.layer.cornerRadius = 24 + +// gesture = UIPanGestureRecognizer(target: self, action: #selector(wasDragged(gestureRecognizer:))) +// viewDrag.addGestureRecognizer(gesture!) + +// let gestureTableView = UIPanGestureRecognizer(target: self, action: #selector(tableviewDrag(gestureRecognizer:))) +// viewContent.addGestureRecognizer(gestureTableView) + + +// activeDrag = false +// shouldScroll = false +// viewDrag.isUserInteractionEnabled = false + // Do any additional setup after loading the view. + self.lblTitle.text = "" + } + override func viewDidLayoutSubviews() { + if let pomint = viewContent.superview?.superview?.convert(hitView.frame.origin, to: nil){ + if(pomint.y >= 0){ + self.viewPicture.transform = CGAffineTransform(translationX: 0, y: pomint.y) + } + } + } + + @IBAction func btnTapped(_ sender: Any) { + + let refreshAlert = UIAlertController(title: "Refresh", message: "All data will be lost.", preferredStyle: UIAlertController.Style.alert) + + refreshAlert.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (action: UIAlertAction!) in + print("Handle Ok logic here") + })) + + refreshAlert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (action: UIAlertAction!) in + print("Handle Cancel Logic here") + })) + + present(refreshAlert, animated: true, completion: nil) + } + + func doTransform(offSet:CGRect) { + let currentY = viewPicture.frame.minY + let perc = viewPicture.frame.height / offSet.minY + +// viewPicture.transform = CGAffineTransform(translationX: 0, y: -currentY*perc) +// let heightValue = 414-offSet.minY +// viewPicture.transform = CGAffineTransform(translationX: 0, y: -heightValue) +// debugPrint("currentY",currentY) +// debugPrint("perc",perc) + } + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + } + */ + func shouldActiveDrag() -> Bool { + return topConstraint.constant != 0 + } + @objc func tableviewDrag(gestureRecognizer: UIPanGestureRecognizer) { + +// switch gestureRecognizer.state { +// case .began: + debugPrint("velocity",gestureRecognizer.velocity(in: self.view)) + let dragVelocity = gestureRecognizer.velocity(in: self.view) + if(dragVelocity.y < 0 && self.topConstraint.constant <= 0){ + self.activeDrag = true + self.wasDragged(gestureRecognizer: gestureRecognizer) + } +// break +// case .ended, .cancelled, .failed , .changed: break +// default:break +// } + } + @objc func wasDragged(gestureRecognizer: UIPanGestureRecognizer) { + // Ensure it's a horizontal drag +// let velocity = gestureRecognizer.velocity(in: self.view) +// if (velocity.y > 0 && self.topConstraint.constant == 0){ +// activeDrag = false +// } + + + let translation = gestureRecognizer.translation(in: self.view) + switch gestureRecognizer.state { + case .began: + initialTranslationY = topConstraint.constant + break + case .changed: + let dy = translation.y + initialTranslationY +// debugPrint("dy",dy) + // 5 + if (abs(dy) <= 200){ + self.topConstraint.constant = min(dy,0) + UIView.animate( + withDuration: 0.5, + delay: 0, + options: UIView.AnimationOptions.curveEaseOut, + animations: { + self.view.layoutIfNeeded() + },completion: { _ in}) + + } + if dy == 0{ + activeDrag = false + } + case .ended, .cancelled, .failed: + self.adjustTheDragView() + break + default: + break + } + } + + func adjustTheDragView(){ + switch self.topConstraint.constant { + case -100 ... 0: + self.topConstraint.constant = 0 + self.activeDrag = false + self.setLockStatus?(false) + default: + self.topConstraint.constant = -200 + self.activeDrag = true + self.setLockStatus?(true) + } + + UIView.animate( + withDuration: 0.5, + delay: 0, + options: UIView.AnimationOptions.curveEaseOut, + animations: { + self.view.layoutIfNeeded() + },completion: { _ in}) + + + let percentage = self.topConstraint.constant / 200 + let dim = abs(0 - abs(percentage)) + debugPrint("dim",dim) + + let opts : UIView.AnimationOptions = .transitionCrossDissolve + UIView.transition(with: self.lblTitle, duration: 0.75, options: opts, animations: { + self.viewNav.alpha = dim + self.lblTitle.text = "Well this sample text" + }, completion: { _ in + self.activeDrag = false + }) + } + + func addImageView() { + guard let topView = UIApplication.shared.windows.first?.rootViewController?.view + else { + print("No root view on which to draw") + return + } +// let viewImage = UIView(frame: CGRect.zero) +// self.view.insertSubview(viewImage, aboveSubview: hitView) +// viewImage.translatesAutoresizingMaskIntoConstraints = false +// +// viewImage.heightAnchor.constraint(equalToConstant: 200).isActive = true +// viewImage.widthAnchor.constraint(equalTo: topView.widthAnchor).isActive = true +// viewImage.centerXAnchor.constraint(equalTo: self.view.centerXAnchor, constant: 0).isActive = true +// viewImage.topAnchor.constraint(equalTo: topView.topAnchor, constant: 0).isActive = true + NSLayoutConstraint.deactivate([topViewImage]) + topViewImageWindow = viewPicture.topAnchor.constraint(equalTo: topView.topAnchor, constant: 0) + topViewImageWindow.isActive = true + viewPicture.transform = CGAffineTransform(translationX: 0, y: 500) +// viewImage.backgroundColor = UIColor.green + } + + func toogleConstraint(isWindowAlign:Bool,diffValue:CGFloat) { + topViewImageWindow.constant = diffValue < 0 ? 0 : max(abs(diffValue),0) + self.viewPicture.layoutIfNeeded() +// UIView.animate( +// withDuration: 0.1, +// delay: 0, +// options: UIView.AnimationOptions.curveEaseOut, +// animations: { +// +// },completion: { _ in}) + +// NSLayoutConstraint.deactivate([topViewImageWindow,topViewImage]) +// if(isWindowAlign){ +// NSLayoutConstraint.activate([topViewImageWindow]) +// topViewImageWindow.constant = diffValue +//// self.viewPicture.transform = CGAffineTransform(translationX: 0, y: ) +// }else{ +// NSLayoutConstraint.activate([topViewImage]) +// topViewImage.constant = max(diffValue,0) +//// self.viewPicture.transform = CGAffineTransform(translationX: 0, y: diffValue) +// } + } + + func imageViewConstraintValue()-> CGFloat { + return topViewImageWindow.constant +// if(topViewImageWindow.isActive){ +// return topViewImageWindow.constant +// }else if(topViewImage.isActive){ +// return topViewImage.constant +// } +// return 0 + } +} + +//extension GalleryDetailsViewController :UITableViewDelegate, UIScrollViewDelegate{ +// func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { +// debugPrint("velocity",scrollView.panGestureRecognizer.velocity(in: self.view)) +// let dragVelocity = scrollView.panGestureRecognizer.velocity(in: self.view) +// if(dragVelocity.y >= 0){ +// self.shouldScroll = false +// self.activeDrag = true +// } +// } +// override func touchesBegan(_ touches: Set, with event: UIEvent?) { +// debugPrint("touchesBegan") +// } +//} + + +class HitView:UIView{ + weak var mapView: MKMapView! + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return mapView.hitTest(point, with: event) +// let hitView = super.hitTest(point, with: event) +// debugPrint("hitView",hitView) +// if hitView == self { return mapView } +// return hitView + + } + +// override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { +// // Only check the subviews that are visible and respond to touches +// let candidates = subviews.filter { +// (view: UIView) -> Bool in +// return !view.isHidden && view.isUserInteractionEnabled && view.alpha >= 0.01 +// } +// // Check from front to back +// for view in candidates.reversed() { +// // Convert to the subview's local coordinate system +// let p = convert(point, toView:view) +// if !view.point(inside: p, with: event) { +// // Not inside the subview, keep looking +// continue +// } +// // If the subview can find a hit target, return that +// if let target = view.hitTest(p, with: event) { +// return target +// } +// } +// // No subview found a hit target if we reach this point +// return nil +// } +} diff --git a/Examples/ImageGallery/ImageGallery/Info.plist b/Examples/ImageGallery/ImageGallery/Info.plist new file mode 100644 index 00000000..2a3483c0 --- /dev/null +++ b/Examples/ImageGallery/ImageGallery/Info.plist @@ -0,0 +1,64 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Examples/ImageGallery/ImageGallery/SceneDelegate.swift b/Examples/ImageGallery/ImageGallery/SceneDelegate.swift new file mode 100644 index 00000000..b88966fd --- /dev/null +++ b/Examples/ImageGallery/ImageGallery/SceneDelegate.swift @@ -0,0 +1,53 @@ +// +// SceneDelegate.swift +// ImageGallery +// +// Created by Ramesh R C on 19.12.19. +// Copyright © 2019 Ramesh R C. All rights reserved. +// + +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + guard let _ = (scene as? UIWindowScene) else { return } + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } + + +} + diff --git a/Examples/ImageGallery/ImageGallery/ViewController.swift b/Examples/ImageGallery/ImageGallery/ViewController.swift new file mode 100644 index 00000000..cc6ce7b3 --- /dev/null +++ b/Examples/ImageGallery/ImageGallery/ViewController.swift @@ -0,0 +1,275 @@ +// +// ViewController.swift +// ImageGallery +// +// Created by Ramesh R C on 19.12.19. +// Copyright © 2019 Ramesh R C. All rights reserved. +// + +import UIKit +import FloatingPanel +import MapKit + +class ViewController: UIViewController { + + var fpc: FloatingPanelController! + var galleryDetailsVC: GalleryDetailsViewController! + var initialColor: UIColor = .black + @IBOutlet var middleImageView: UIView! + @IBOutlet var topNavView: UIView! + var imageViewTopConstraint: NSLayoutConstraint! + var imageViewTopSuperViewConstraint: NSLayoutConstraint! + var inistalMoveValue:CGFloat = -100 + let ppp = FloatingPanelHotelBehavior() + @IBOutlet weak var mapView: MKMapView! + var xpostion: CGFloat = 0.0 + var isLock:Bool = false + var panelHotelLayout:FloatingPanelHotelLayout? + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + + initialColor = view.backgroundColor! + // Initialize FloatingPanelController + fpc = FloatingPanelController() + fpc.delegate = self + + // Initialize FloatingPanelController and add the view + fpc.surfaceView.backgroundColor = .clear + fpc.surfaceView.cornerRadius = 0.0 + fpc.surfaceView.shadowHidden = true + fpc.surfaceView.borderWidth = 1.0 / traitCollection.displayScale + fpc.surfaceView.borderColor = UIColor.black.withAlphaComponent(0.2) + fpc.surfaceView.clipsToBounds = true + fpc.view.clipsToBounds = true + fpc.surfaceView.grabberHandle.isHidden = true + galleryDetailsVC = storyboard?.instantiateViewController(withIdentifier: "GalleryDetailsViewController") as? GalleryDetailsViewController + + // Set a content view controller + fpc.set(contentViewController: galleryDetailsVC) + fpc.track(scrollView: galleryDetailsVC.viewContent) + fpc.addPanel(toParent: self, belowView: nil, animated: false) + galleryDetailsVC.hitView.mapView = self.mapView + if let _ = galleryDetailsVC.setLockStatus{ + galleryDetailsVC.setLockStatus = { isLock in + self.isLock = isLock + } + } + galleryDetailsVC.addImageView() + } + override func viewDidAppear(_ animated: Bool) { + + } + override func viewDidDisappear(_ animated: Bool) { + + } + +} + + extension ViewController : FloatingPanelControllerDelegate{ + func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? { + panelHotelLayout = FloatingPanelHotelLayout(middle: self.view.frame.height/2, topBuffer: 200) + return panelHotelLayout + } + + func floatingPanel(_ vc: FloatingPanelController, behaviorFor newCollection: UITraitCollection) -> FloatingPanelBehavior? { + return FloatingPanelHotelBehavior() + } +// func floatingPanelShouldBeginDragging(_ vc: FloatingPanelController) -> Bool { +// debugPrint("isLock",isLock) +// return !isLock +// } + func floatingPanelDidEndDecelerating(_ vc: FloatingPanelController) { +// self.doViewImageMove(vc) +// if (vc.position == .full){ +// galleryDetailsVC.toogleConstraint(isWindowAlign:true,diffValue: 0) +// }else if (vc.position == .half){ galleryDetailsVC.toogleConstraint(isWindowAlign:true,diffValue: 0) +// } +// else if (vc.position == .tip){ galleryDetailsVC.toogleConstraint(isWindowAlign:true,diffValue: 500) +// } + +// let diff = 414 - vc.surfaceView.frame.minY +// var ppdiff:CGFloat = -50 +// if(diff < galleryDetailsVC.viewPicture.frame.height){ +//// galleryDetailsVC.viewPicture.transform = CGAffineTransform(translationX: 0, y: -diff) +// ppdiff = diff +// } +// if (diff < 0){ +//// galleryDetailsVC.viewPicture.transform = CGAffineTransform(translationX: 0, y: -50) +// ppdiff = -50 +// } +// if(vc.position == .half || +// vc.position == .full){ +// galleryDetailsVC.toogleConstraint(isWindowAlign:true,diffValue: ppdiff) +// }else{ +// galleryDetailsVC.toogleConstraint(isWindowAlign:false,diffValue: ppdiff) +// } + + + + + + +// if(vc.position == .full){ +// isLock = true +// } +// debugPrint("vc.surfaceView.frame.minY",vc.surfaceView.frame.minY) +// if vc.surfaceView.frame.minY == 0 { +//// galleryDetailsVC.viewContent.isUserInteractionEnabled = true +// +//// let diff = self.view.frame.height - galleryDetailsVC.viewContent.frame.minY +//// debugPrint("galleryDetailsVC.viewContent.frame.minY",galleryDetailsVC.viewContent.frame.minY) +//// debugPrint("translation",vc.panGestureRecognizer.translation(in: self.view)) +//// galleryDetailsVC.viewContent.transform = CGAffineTransform(scaleX: 0, y: -diff) +// }else{ +//// galleryDetailsVC.viewContent.transform = CGAffineTransform(scaleX: 0, y: 0) +// } + } +// func floatingPanelDidChangePosition(_ vc: FloatingPanelController) { +// if(vc.position == .full){ +// galleryDetailsVC.activeDrag = true +// } +// } + func floatingPanelWillBeginDragging(_ vc: FloatingPanelController) { + panelHotelLayout?.initialTranslationY = galleryDetailsVC.imageViewConstraintValue() + } + func floatingPanelDidEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetPosition: FloatingPanelPosition) { + } + func floatingPanelDidMove(_ vc: FloatingPanelController) { +// self.doViewImageMove(vc) + } + + func doViewImageMove(_ vc: FloatingPanelController){ + let translation = vc.panGestureRecognizer.translation(in: self.view) + let velocity = vc.panGestureRecognizer.velocity(in: self.view) + + let dy = (panelHotelLayout?.initialTranslationY ?? 0) - abs(translation.y) + //abs(translation.y) - (panelHotelLayout?.initialTranslationY ?? 0) +// debugPrint("dy",dy) +// debugPrint("velocity",velocity.y) +// debugPrint("translation.y",translation.y) +// debugPrint("perce Y:",translation.y / vc.surfaceView.frame.height) + let diff_1 = 500 - vc.surfaceView.frame.minY + let diff = 500 - abs(translation.y) + var ppdiff:CGFloat = -50 + if(diff < galleryDetailsVC.viewPicture.frame.height){ +// galleryDetailsVC.viewPicture.transform = CGAffineTransform(translationX: 0, y: -diff) + ppdiff = diff + } + if (diff < 0){ +// galleryDetailsVC.viewPicture.transform = CGAffineTransform(translationX: 0, y: -50) + ppdiff = -50 + } +// debugPrint("ppdiff",diff) + let ddd = diff_1 - 500 + debugPrint("diff_1",diff_1) + debugPrint("ddd",ddd) + galleryDetailsVC.viewPicture.transform = CGAffineTransform(translationX: 0, y: vc.surfaceView.frame.minY) + +// galleryDetailsVC.toogleConstraint(isWindowAlign:true,diffValue: vc.surfaceView.frame.minY) + } + + } + + +class FloatingPanelHotelLayout: FloatingPanelLayout { + var initialTranslationY: CGFloat = 0 + var positionReference: FloatingPanelLayoutReference { + return .fromSuperview + } + var initialPosition: FloatingPanelPosition { + return .tip + } + + var topInteractionBuffer: CGFloat { return self.topBuffer } + var bottomInteractionBuffer: CGFloat { return 0.0 } + var mm:CGFloat = 262.0 + var topBuffer:CGFloat = 200 + + init(middle:CGFloat,topBuffer:CGFloat) { + self.mm = middle + self.topBuffer = topBuffer + } + func insetFor(position: FloatingPanelPosition) -> CGFloat? { + switch position { + case .full: return -self.topBuffer + case .half: return UIScreen.main.bounds.height + case .tip: return 500 // Visible + ToolView + default: return nil + } + } + func backdropAlphaFor(position: FloatingPanelPosition) -> CGFloat { + return 0.0 + } +} + + +class FloatingPanelHotelBehavior: FloatingPanelBehavior { + var velocityThreshold: CGFloat { + return 15.0 + } + + func interactionAnimator(_ fpc: FloatingPanelController, to targetPosition: FloatingPanelPosition, with velocity: CGVector) -> UIViewPropertyAnimator { + let timing = timeingCurve(to: targetPosition, with: velocity) + return UIViewPropertyAnimator(duration: 0, timingParameters: timing) + } + + private func timeingCurve(to: FloatingPanelPosition, with velocity: CGVector) -> UITimingCurveProvider { + let damping = self.damping(with: velocity) + return UISpringTimingParameters(dampingRatio: damping, + frequencyResponse: 0.4, + initialVelocity: velocity) + } + + private func damping(with velocity: CGVector) -> CGFloat { + switch velocity.dy { + case ...(-velocityThreshold): + return 0.7 + case velocityThreshold...: + return 0.7 + default: + return 1.0 + } + } +} + + + + +protocol LayoutGuideProvider { + var topAnchor: NSLayoutYAxisAnchor { get } + var bottomAnchor: NSLayoutYAxisAnchor { get } +} +extension UILayoutGuide: LayoutGuideProvider {} + +class CustomLayoutGuide: LayoutGuideProvider { + let topAnchor: NSLayoutYAxisAnchor + let bottomAnchor: NSLayoutYAxisAnchor + init(topAnchor: NSLayoutYAxisAnchor, bottomAnchor: NSLayoutYAxisAnchor) { + self.topAnchor = topAnchor + self.bottomAnchor = bottomAnchor + } +} + +extension UIViewController { + var layoutInsets: UIEdgeInsets { + if #available(iOS 11.0, *) { + return view.safeAreaInsets + } else { + return UIEdgeInsets(top: topLayoutGuide.length, + left: 0.0, + bottom: bottomLayoutGuide.length, + right: 0.0) + } + } + + var layoutGuide: LayoutGuideProvider { + if #available(iOS 11.0, *) { + return view!.safeAreaLayoutGuide + } else { + return CustomLayoutGuide(topAnchor: topLayoutGuide.bottomAnchor, + bottomAnchor: bottomLayoutGuide.topAnchor) + } + } +} diff --git a/Examples/Maps/Maps/ViewController.swift b/Examples/Maps/Maps/ViewController.swift new file mode 100644 index 00000000..29d09bb4 --- /dev/null +++ b/Examples/Maps/Maps/ViewController.swift @@ -0,0 +1,287 @@ +// +// Copyright © 2018 Shin Yamamoto. All rights reserved. +// + +import UIKit +import MapKit +import FloatingPanel + +class ViewController: UIViewController, MKMapViewDelegate, UISearchBarDelegate, FloatingPanelControllerDelegate { + var fpc: FloatingPanelController! + var searchVC: SearchPanelViewController! + + @IBOutlet weak var mapView: MKMapView! + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view, typically from a nib. + // Initialize FloatingPanelController + fpc = FloatingPanelController() + fpc.delegate = self + + // Initialize FloatingPanelController and add the view + fpc.surfaceView.backgroundColor = .clear + if #available(iOS 11, *) { + fpc.surfaceView.cornerRadius = 9.0 + } else { + fpc.surfaceView.cornerRadius = 0.0 + } + fpc.surfaceView.shadowHidden = false + + searchVC = storyboard?.instantiateViewController(withIdentifier: "SearchPanel") as? SearchPanelViewController + + // Set a content view controller + fpc.set(contentViewController: searchVC) + fpc.track(scrollView: searchVC.tableView) + + setupMapView() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + // Add FloatingPanel to a view with animation. + fpc.addPanel(toParent: self, animated: true) + + // Must be here + searchVC.searchBar.delegate = self + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + teardownMapView() + } + + func setupMapView() { + let center = CLLocationCoordinate2D(latitude: 37.623198015869235, + longitude: -122.43066818432008) + let span = MKCoordinateSpan(latitudeDelta: 0.4425100023575723, + longitudeDelta: 0.28543697435880233) + let region = MKCoordinateRegion(center: center, span: span) + mapView.region = region + mapView.showsCompass = true + mapView.showsUserLocation = true + mapView.delegate = self + } + + func teardownMapView() { + // Prevent a crash + mapView.delegate = nil + mapView = nil + } + + // MARK: UISearchBarDelegate + + func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { + searchBar.resignFirstResponder() + searchBar.showsCancelButton = false + searchVC.hideHeader() + fpc.move(to: .half, animated: true) + } + + func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) { + searchBar.showsCancelButton = true + searchVC.showHeader() + searchVC.tableView.alpha = 1.0 + fpc.move(to: .full, animated: true) + } + + // MARK: FloatingPanelControllerDelegate + + func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? { + switch newCollection.verticalSizeClass { + case .compact: + fpc.surfaceView.borderWidth = 1.0 / traitCollection.displayScale + fpc.surfaceView.borderColor = UIColor.black.withAlphaComponent(0.2) + return SearchPanelLandscapeLayout() + default: + fpc.surfaceView.borderWidth = 0.0 + fpc.surfaceView.borderColor = nil + return nil + } + } + + func floatingPanelDidMove(_ vc: FloatingPanelController) { + let y = vc.surfaceView.frame.origin.y + let tipY = vc.originYOfSurface(for: .tip) + if y > tipY - 44.0 { + let progress = max(0.0, min((tipY - y) / 44.0, 1.0)) + self.searchVC.tableView.alpha = progress + } + debugPrint("NearbyPosition : ",vc.nearbyPosition) + } + + func floatingPanelWillBeginDragging(_ vc: FloatingPanelController) { + if vc.position == .full { + searchVC.searchBar.showsCancelButton = false + searchVC.searchBar.resignFirstResponder() + } + } + + func floatingPanelDidEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetPosition: FloatingPanelPosition) { + if targetPosition != .full { + searchVC.hideHeader() + } + + UIView.animate(withDuration: 0.25, + delay: 0.0, + options: .allowUserInteraction, + animations: { + if targetPosition == .tip { + self.searchVC.tableView.alpha = 0.0 + } else { + self.searchVC.tableView.alpha = 1.0 + } + }, completion: nil) + } +} + +class SearchPanelViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { + + @IBOutlet weak var tableView: UITableView! + @IBOutlet weak var searchBar: UISearchBar! + @IBOutlet weak var visualEffectView: UIVisualEffectView! + + // For iOS 10 only + private lazy var shadowLayer: CAShapeLayer = CAShapeLayer() + + override func viewDidLoad() { + super.viewDidLoad() + tableView.dataSource = self + tableView.delegate = self + searchBar.placeholder = "Search for a place or address" + searchBar.setSearchText(fontSize: 15.0) + + hideHeader() + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + if #available(iOS 11, *) { + } else { + // Exmaple: Add rounding corners on iOS 10 + visualEffectView.layer.cornerRadius = 9.0 + visualEffectView.clipsToBounds = true + + // Exmaple: Add shadow manually on iOS 10 + view.layer.insertSublayer(shadowLayer, at: 0) + let rect = visualEffectView.frame + let path = UIBezierPath(roundedRect: rect, + byRoundingCorners: [.topLeft, .topRight], + cornerRadii: CGSize(width: 9.0, height: 9.0)) + shadowLayer.frame = visualEffectView.frame + shadowLayer.shadowPath = path.cgPath + shadowLayer.shadowColor = UIColor.black.cgColor + shadowLayer.shadowOffset = CGSize(width: 0.0, height: 1.0) + shadowLayer.shadowOpacity = 0.2 + shadowLayer.shadowRadius = 3.0 + } + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return 2 + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) + if let cell = cell as? SearchCell { + switch indexPath.row { + case 0: + cell.iconImageView.image = UIImage(named: "mark") + cell.titleLabel.text = "Marked Location" + cell.subTitleLabel.text = "Golden Gate Bridge, San Francisco" + case 1: + cell.iconImageView.image = UIImage(named: "like") + cell.titleLabel.text = "Favorites" + cell.subTitleLabel.text = "0 Places" + default: + break + } + } + return cell + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: false) + } + + func showHeader() { + changeHeader(height: 116.0) + } + + func hideHeader() { + changeHeader(height: 0.0) + } + + func changeHeader(height: CGFloat) { + tableView.beginUpdates() + if let headerView = tableView.tableHeaderView { + UIView.animate(withDuration: 0.25) { + var frame = headerView.frame + frame.size.height = height + self.tableView.tableHeaderView?.frame = frame + } + } + tableView.endUpdates() + } +} + +public class SearchPanelLandscapeLayout: FloatingPanelLayout { + public var initialPosition: FloatingPanelPosition { + return .tip + } + + public var supportedPositions: Set { + return [.full, .tip] + } + + public func insetFor(position: FloatingPanelPosition) -> CGFloat? { + switch position { + case .full: return 16.0 + case .tip: return 69.0 + default: return nil + } + } + + public func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] { + if #available(iOS 11.0, *) { + return [ + surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 8.0), + surfaceView.widthAnchor.constraint(equalToConstant: 291), + ] + } else { + return [ + surfaceView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8.0), + surfaceView.widthAnchor.constraint(equalToConstant: 291), + ] + } + } + + public func backdropAlphaFor(position: FloatingPanelPosition) -> CGFloat { + return 0.0 + } +} + +class SearchCell: UITableViewCell { + @IBOutlet weak var iconImageView: UIImageView! + @IBOutlet weak var titleLabel: UILabel! + @IBOutlet weak var subTitleLabel: UILabel! +} + +class SearchHeaderView: UIView { + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + self.clipsToBounds = true + } +} + +extension UISearchBar { + func setSearchText(fontSize: CGFloat) { + #if swift(>=5.1) // Xcode 11 or later + let font = searchTextField.font + searchTextField.font = font?.withSize(fontSize) + #else + let textField = value(forKey: "_searchField") as! UITextField + textField.font = textField.font?.withSize(fontSize) + #endif + } +} diff --git a/Examples/MiddlePanel/MiddlePanel.xcodeproj/project.pbxproj b/Examples/MiddlePanel/MiddlePanel.xcodeproj/project.pbxproj new file mode 100644 index 00000000..c24959c9 --- /dev/null +++ b/Examples/MiddlePanel/MiddlePanel.xcodeproj/project.pbxproj @@ -0,0 +1,613 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 580857C923A19F4C00D7A4BD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580857C823A19F4C00D7A4BD /* AppDelegate.swift */; }; + 580857CB23A19F4C00D7A4BD /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580857CA23A19F4C00D7A4BD /* SceneDelegate.swift */; }; + 580857CD23A19F4C00D7A4BD /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580857CC23A19F4C00D7A4BD /* ViewController.swift */; }; + 580857D023A19F4C00D7A4BD /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 580857CE23A19F4C00D7A4BD /* Main.storyboard */; }; + 580857D223A19F4F00D7A4BD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 580857D123A19F4F00D7A4BD /* Assets.xcassets */; }; + 580857D523A19F4F00D7A4BD /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 580857D323A19F4F00D7A4BD /* LaunchScreen.storyboard */; }; + 580857E023A19F4F00D7A4BD /* MiddlePanelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580857DF23A19F4F00D7A4BD /* MiddlePanelTests.swift */; }; + 580857EB23A19F4F00D7A4BD /* MiddlePanelUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580857EA23A19F4F00D7A4BD /* MiddlePanelUITests.swift */; }; + 580857F923A19FF800D7A4BD /* ImageListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580857F823A19FF800D7A4BD /* ImageListViewController.swift */; }; + 580857FB23A1A1EB00D7A4BD /* HotelDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580857FA23A1A1EB00D7A4BD /* HotelDetailsViewController.swift */; }; + 580857FE23A1A3CC00D7A4BD /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 580857FD23A1A3CC00D7A4BD /* FloatingPanel.framework */; }; + 58E500AB23A41DFE006E29E9 /* SlideViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E500AA23A41DFE006E29E9 /* SlideViewController.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 580857DC23A19F4F00D7A4BD /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 580857BD23A19F4C00D7A4BD /* Project object */; + proxyType = 1; + remoteGlobalIDString = 580857C423A19F4C00D7A4BD; + remoteInfo = MiddlePanel; + }; + 580857E723A19F4F00D7A4BD /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 580857BD23A19F4C00D7A4BD /* Project object */; + proxyType = 1; + remoteGlobalIDString = 580857C423A19F4C00D7A4BD; + remoteInfo = MiddlePanel; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 580857C523A19F4C00D7A4BD /* MiddlePanel.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MiddlePanel.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 580857C823A19F4C00D7A4BD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 580857CA23A19F4C00D7A4BD /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 580857CC23A19F4C00D7A4BD /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 580857CF23A19F4C00D7A4BD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 580857D123A19F4F00D7A4BD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 580857D423A19F4F00D7A4BD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 580857D623A19F4F00D7A4BD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 580857DB23A19F4F00D7A4BD /* MiddlePanelTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MiddlePanelTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 580857DF23A19F4F00D7A4BD /* MiddlePanelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiddlePanelTests.swift; sourceTree = ""; }; + 580857E123A19F4F00D7A4BD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 580857E623A19F4F00D7A4BD /* MiddlePanelUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MiddlePanelUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 580857EA23A19F4F00D7A4BD /* MiddlePanelUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiddlePanelUITests.swift; sourceTree = ""; }; + 580857EC23A19F4F00D7A4BD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 580857F823A19FF800D7A4BD /* ImageListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageListViewController.swift; sourceTree = ""; }; + 580857FA23A1A1EB00D7A4BD /* HotelDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HotelDetailsViewController.swift; sourceTree = ""; }; + 580857FD23A1A3CC00D7A4BD /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 58E500AA23A41DFE006E29E9 /* SlideViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideViewController.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 580857C223A19F4C00D7A4BD /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 580857FE23A1A3CC00D7A4BD /* FloatingPanel.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 580857D823A19F4F00D7A4BD /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 580857E323A19F4F00D7A4BD /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 580857BC23A19F4C00D7A4BD = { + isa = PBXGroup; + children = ( + 580857C723A19F4C00D7A4BD /* MiddlePanel */, + 580857DE23A19F4F00D7A4BD /* MiddlePanelTests */, + 580857E923A19F4F00D7A4BD /* MiddlePanelUITests */, + 580857C623A19F4C00D7A4BD /* Products */, + 580857FC23A1A3CC00D7A4BD /* Frameworks */, + ); + sourceTree = ""; + }; + 580857C623A19F4C00D7A4BD /* Products */ = { + isa = PBXGroup; + children = ( + 580857C523A19F4C00D7A4BD /* MiddlePanel.app */, + 580857DB23A19F4F00D7A4BD /* MiddlePanelTests.xctest */, + 580857E623A19F4F00D7A4BD /* MiddlePanelUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 580857C723A19F4C00D7A4BD /* MiddlePanel */ = { + isa = PBXGroup; + children = ( + 580857C823A19F4C00D7A4BD /* AppDelegate.swift */, + 580857CA23A19F4C00D7A4BD /* SceneDelegate.swift */, + 580857CC23A19F4C00D7A4BD /* ViewController.swift */, + 580857F823A19FF800D7A4BD /* ImageListViewController.swift */, + 580857CE23A19F4C00D7A4BD /* Main.storyboard */, + 580857D123A19F4F00D7A4BD /* Assets.xcassets */, + 580857D323A19F4F00D7A4BD /* LaunchScreen.storyboard */, + 580857D623A19F4F00D7A4BD /* Info.plist */, + 580857FA23A1A1EB00D7A4BD /* HotelDetailsViewController.swift */, + 58E500AA23A41DFE006E29E9 /* SlideViewController.swift */, + ); + path = MiddlePanel; + sourceTree = ""; + }; + 580857DE23A19F4F00D7A4BD /* MiddlePanelTests */ = { + isa = PBXGroup; + children = ( + 580857DF23A19F4F00D7A4BD /* MiddlePanelTests.swift */, + 580857E123A19F4F00D7A4BD /* Info.plist */, + ); + path = MiddlePanelTests; + sourceTree = ""; + }; + 580857E923A19F4F00D7A4BD /* MiddlePanelUITests */ = { + isa = PBXGroup; + children = ( + 580857EA23A19F4F00D7A4BD /* MiddlePanelUITests.swift */, + 580857EC23A19F4F00D7A4BD /* Info.plist */, + ); + path = MiddlePanelUITests; + sourceTree = ""; + }; + 580857FC23A1A3CC00D7A4BD /* Frameworks */ = { + isa = PBXGroup; + children = ( + 580857FD23A1A3CC00D7A4BD /* FloatingPanel.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 580857C423A19F4C00D7A4BD /* MiddlePanel */ = { + isa = PBXNativeTarget; + buildConfigurationList = 580857EF23A19F4F00D7A4BD /* Build configuration list for PBXNativeTarget "MiddlePanel" */; + buildPhases = ( + 580857C123A19F4C00D7A4BD /* Sources */, + 580857C223A19F4C00D7A4BD /* Frameworks */, + 580857C323A19F4C00D7A4BD /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = MiddlePanel; + productName = MiddlePanel; + productReference = 580857C523A19F4C00D7A4BD /* MiddlePanel.app */; + productType = "com.apple.product-type.application"; + }; + 580857DA23A19F4F00D7A4BD /* MiddlePanelTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 580857F223A19F4F00D7A4BD /* Build configuration list for PBXNativeTarget "MiddlePanelTests" */; + buildPhases = ( + 580857D723A19F4F00D7A4BD /* Sources */, + 580857D823A19F4F00D7A4BD /* Frameworks */, + 580857D923A19F4F00D7A4BD /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 580857DD23A19F4F00D7A4BD /* PBXTargetDependency */, + ); + name = MiddlePanelTests; + productName = MiddlePanelTests; + productReference = 580857DB23A19F4F00D7A4BD /* MiddlePanelTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 580857E523A19F4F00D7A4BD /* MiddlePanelUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 580857F523A19F4F00D7A4BD /* Build configuration list for PBXNativeTarget "MiddlePanelUITests" */; + buildPhases = ( + 580857E223A19F4F00D7A4BD /* Sources */, + 580857E323A19F4F00D7A4BD /* Frameworks */, + 580857E423A19F4F00D7A4BD /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 580857E823A19F4F00D7A4BD /* PBXTargetDependency */, + ); + name = MiddlePanelUITests; + productName = MiddlePanelUITests; + productReference = 580857E623A19F4F00D7A4BD /* MiddlePanelUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 580857BD23A19F4C00D7A4BD /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1110; + LastUpgradeCheck = 1110; + ORGANIZATIONNAME = "Ramesh R C"; + TargetAttributes = { + 580857C423A19F4C00D7A4BD = { + CreatedOnToolsVersion = 11.1; + }; + 580857DA23A19F4F00D7A4BD = { + CreatedOnToolsVersion = 11.1; + TestTargetID = 580857C423A19F4C00D7A4BD; + }; + 580857E523A19F4F00D7A4BD = { + CreatedOnToolsVersion = 11.1; + TestTargetID = 580857C423A19F4C00D7A4BD; + }; + }; + }; + buildConfigurationList = 580857C023A19F4C00D7A4BD /* Build configuration list for PBXProject "MiddlePanel" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 580857BC23A19F4C00D7A4BD; + productRefGroup = 580857C623A19F4C00D7A4BD /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 580857C423A19F4C00D7A4BD /* MiddlePanel */, + 580857DA23A19F4F00D7A4BD /* MiddlePanelTests */, + 580857E523A19F4F00D7A4BD /* MiddlePanelUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 580857C323A19F4C00D7A4BD /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 580857D523A19F4F00D7A4BD /* LaunchScreen.storyboard in Resources */, + 580857D223A19F4F00D7A4BD /* Assets.xcassets in Resources */, + 580857D023A19F4C00D7A4BD /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 580857D923A19F4F00D7A4BD /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 580857E423A19F4F00D7A4BD /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 580857C123A19F4C00D7A4BD /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 580857CD23A19F4C00D7A4BD /* ViewController.swift in Sources */, + 580857C923A19F4C00D7A4BD /* AppDelegate.swift in Sources */, + 58E500AB23A41DFE006E29E9 /* SlideViewController.swift in Sources */, + 580857FB23A1A1EB00D7A4BD /* HotelDetailsViewController.swift in Sources */, + 580857F923A19FF800D7A4BD /* ImageListViewController.swift in Sources */, + 580857CB23A19F4C00D7A4BD /* SceneDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 580857D723A19F4F00D7A4BD /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 580857E023A19F4F00D7A4BD /* MiddlePanelTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 580857E223A19F4F00D7A4BD /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 580857EB23A19F4F00D7A4BD /* MiddlePanelUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 580857DD23A19F4F00D7A4BD /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 580857C423A19F4C00D7A4BD /* MiddlePanel */; + targetProxy = 580857DC23A19F4F00D7A4BD /* PBXContainerItemProxy */; + }; + 580857E823A19F4F00D7A4BD /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 580857C423A19F4C00D7A4BD /* MiddlePanel */; + targetProxy = 580857E723A19F4F00D7A4BD /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 580857CE23A19F4C00D7A4BD /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 580857CF23A19F4C00D7A4BD /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 580857D323A19F4F00D7A4BD /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 580857D423A19F4F00D7A4BD /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 580857ED23A19F4F00D7A4BD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.1; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 580857EE23A19F4F00D7A4BD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.1; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 580857F023A19F4F00D7A4BD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = MiddlePanel/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.rc.MiddlePanel; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 580857F123A19F4F00D7A4BD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = MiddlePanel/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.rc.MiddlePanel; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 580857F323A19F4F00D7A4BD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = MiddlePanelTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.rc.MiddlePanelTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MiddlePanel.app/MiddlePanel"; + }; + name = Debug; + }; + 580857F423A19F4F00D7A4BD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = MiddlePanelTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.rc.MiddlePanelTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MiddlePanel.app/MiddlePanel"; + }; + name = Release; + }; + 580857F623A19F4F00D7A4BD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = MiddlePanelUITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.rc.MiddlePanelUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = MiddlePanel; + }; + name = Debug; + }; + 580857F723A19F4F00D7A4BD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = MiddlePanelUITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.rc.MiddlePanelUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = MiddlePanel; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 580857C023A19F4C00D7A4BD /* Build configuration list for PBXProject "MiddlePanel" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 580857ED23A19F4F00D7A4BD /* Debug */, + 580857EE23A19F4F00D7A4BD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 580857EF23A19F4F00D7A4BD /* Build configuration list for PBXNativeTarget "MiddlePanel" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 580857F023A19F4F00D7A4BD /* Debug */, + 580857F123A19F4F00D7A4BD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 580857F223A19F4F00D7A4BD /* Build configuration list for PBXNativeTarget "MiddlePanelTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 580857F323A19F4F00D7A4BD /* Debug */, + 580857F423A19F4F00D7A4BD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 580857F523A19F4F00D7A4BD /* Build configuration list for PBXNativeTarget "MiddlePanelUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 580857F623A19F4F00D7A4BD /* Debug */, + 580857F723A19F4F00D7A4BD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 580857BD23A19F4C00D7A4BD /* Project object */; +} diff --git a/Examples/MiddlePanel/MiddlePanel/AppDelegate.swift b/Examples/MiddlePanel/MiddlePanel/AppDelegate.swift new file mode 100644 index 00000000..6ceaca1c --- /dev/null +++ b/Examples/MiddlePanel/MiddlePanel/AppDelegate.swift @@ -0,0 +1,37 @@ +// +// AppDelegate.swift +// MiddlePanel +// +// Created by Ramesh R C on 11.12.19. +// Copyright © 2019 Ramesh R C. All rights reserved. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } + + +} + diff --git a/Examples/MiddlePanel/MiddlePanel/Assets.xcassets/AppIcon.appiconset/Contents.json b/Examples/MiddlePanel/MiddlePanel/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d8db8d65 --- /dev/null +++ b/Examples/MiddlePanel/MiddlePanel/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Examples/MiddlePanel/MiddlePanel/Assets.xcassets/Contents.json b/Examples/MiddlePanel/MiddlePanel/Assets.xcassets/Contents.json new file mode 100644 index 00000000..da4a164c --- /dev/null +++ b/Examples/MiddlePanel/MiddlePanel/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Examples/MiddlePanel/MiddlePanel/Assets.xcassets/Sample-Image.imageset/Contents.json b/Examples/MiddlePanel/MiddlePanel/Assets.xcassets/Sample-Image.imageset/Contents.json new file mode 100644 index 00000000..50d2aec0 --- /dev/null +++ b/Examples/MiddlePanel/MiddlePanel/Assets.xcassets/Sample-Image.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Sample-Image.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Examples/MiddlePanel/MiddlePanel/Assets.xcassets/Sample-Image.imageset/Sample-Image.png b/Examples/MiddlePanel/MiddlePanel/Assets.xcassets/Sample-Image.imageset/Sample-Image.png new file mode 100644 index 00000000..bcde0d0d Binary files /dev/null and b/Examples/MiddlePanel/MiddlePanel/Assets.xcassets/Sample-Image.imageset/Sample-Image.png differ diff --git a/Examples/MiddlePanel/MiddlePanel/Base.lproj/LaunchScreen.storyboard b/Examples/MiddlePanel/MiddlePanel/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..865e9329 --- /dev/null +++ b/Examples/MiddlePanel/MiddlePanel/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/MiddlePanel/MiddlePanel/Base.lproj/Main.storyboard b/Examples/MiddlePanel/MiddlePanel/Base.lproj/Main.storyboard new file mode 100644 index 00000000..d56a6fe7 --- /dev/null +++ b/Examples/MiddlePanel/MiddlePanel/Base.lproj/Main.storyboard @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/MiddlePanel/MiddlePanel/HotelDetailsViewController.swift b/Examples/MiddlePanel/MiddlePanel/HotelDetailsViewController.swift new file mode 100644 index 00000000..7415e17c --- /dev/null +++ b/Examples/MiddlePanel/MiddlePanel/HotelDetailsViewController.swift @@ -0,0 +1,30 @@ +// +// HotelDetails.swift +// MiddlePanel +// +// Created by Ramesh R C on 11.12.19. +// Copyright © 2019 Ramesh R C. All rights reserved. +// + +import UIKit + +class HotelDetailsViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + } + + + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + } + */ + +} diff --git a/Examples/MiddlePanel/MiddlePanel/ImageListViewController.swift b/Examples/MiddlePanel/MiddlePanel/ImageListViewController.swift new file mode 100644 index 00000000..4bf94b12 --- /dev/null +++ b/Examples/MiddlePanel/MiddlePanel/ImageListViewController.swift @@ -0,0 +1,30 @@ +// +// ImageListViewController.swift +// MiddlePanel +// +// Created by Ramesh R C on 11.12.19. +// Copyright © 2019 Ramesh R C. All rights reserved. +// + +import UIKit + +class ImageListViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + } + + + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + } + */ + +} diff --git a/Examples/MiddlePanel/MiddlePanel/Info.plist b/Examples/MiddlePanel/MiddlePanel/Info.plist new file mode 100644 index 00000000..2a3483c0 --- /dev/null +++ b/Examples/MiddlePanel/MiddlePanel/Info.plist @@ -0,0 +1,64 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Examples/MiddlePanel/MiddlePanel/SceneDelegate.swift b/Examples/MiddlePanel/MiddlePanel/SceneDelegate.swift new file mode 100644 index 00000000..c09e64f6 --- /dev/null +++ b/Examples/MiddlePanel/MiddlePanel/SceneDelegate.swift @@ -0,0 +1,53 @@ +// +// SceneDelegate.swift +// MiddlePanel +// +// Created by Ramesh R C on 11.12.19. +// Copyright © 2019 Ramesh R C. All rights reserved. +// + +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + guard let _ = (scene as? UIWindowScene) else { return } + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } + + +} + diff --git a/Examples/MiddlePanel/MiddlePanel/SlideViewController.swift b/Examples/MiddlePanel/MiddlePanel/SlideViewController.swift new file mode 100644 index 00000000..a23fb719 --- /dev/null +++ b/Examples/MiddlePanel/MiddlePanel/SlideViewController.swift @@ -0,0 +1,221 @@ +// +// SlideViewController.swift +// MiddlePanel +// +// Created by Ramesh R C on 13.12.19. +// Copyright © 2019 Ramesh R C. All rights reserved. +// + +import UIKit +import FloatingPanel + +class SlideViewController: UIViewController { + + var fpc: FloatingPanelController! + var hotelDetailsVC: HotelDetailsViewController! + var initialColor: UIColor = .black + var imageViewTopConstraint: NSLayoutConstraint! + var imageViewTopSuperViewConstraint: NSLayoutConstraint! + var inistalMoveValue:CGFloat = -100 + let ppp = FloatingPanelHotelBehavior() + + @IBOutlet var middleImageView: UIView! + @IBOutlet var topNavView: UIView! + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + + imageViewTopSuperViewConstraint = middleImageView.topAnchor.constraint(equalTo: view.topAnchor, constant:0) + initialColor = view.backgroundColor! + // Initialize FloatingPanelController + fpc = FloatingPanelController() + fpc.delegate = self + + // Initialize FloatingPanelController and add the view + fpc.surfaceView.backgroundColor = UIColor(displayP3Red: 30.0/255.0, green: 30.0/255.0, blue: 30.0/255.0, alpha: 1.0) + fpc.surfaceView.cornerRadius = 24.0 + fpc.surfaceView.shadowHidden = true + fpc.surfaceView.borderWidth = 1.0 / traitCollection.displayScale + fpc.surfaceView.borderColor = UIColor.black.withAlphaComponent(0.2) + + hotelDetailsVC = storyboard?.instantiateViewController(withIdentifier: "HotelDetailsVC") as? HotelDetailsViewController + + // Set a content view controller + fpc.set(contentViewController: hotelDetailsVC) +// fpc.track(scrollView: newsVC.scrollView) + fpc.addPanel(toParent: self, belowView: nil, animated: false) + + middleImageView.frame = .zero + fpc.view.addSubview(middleImageView) +// fpc.surfaceView.insertSubview(middleImageView, belowSubview: fpc.surfaceView) + middleImageView.translatesAutoresizingMaskIntoConstraints = false + imageViewTopConstraint = middleImageView.topAnchor.constraint(equalTo: fpc.surfaceView.topAnchor, constant: inistalMoveValue) + + NSLayoutConstraint.activate([ + imageViewTopSuperViewConstraint, + middleImageView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 0.0), + middleImageView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: 0.0), + middleImageView.heightAnchor.constraint(equalToConstant: 200) + ]) + fpc.view.bringSubviewToFront(fpc.surfaceView) + + } + override func viewDidAppear(_ animated: Bool) { + let visibleHeight = fpc.surfaceView.frame.height - fpc.surfaceView.frame.minY + inistalMoveValue = fpc.surfaceView.frame.minY - (visibleHeight/2) + imageViewTopSuperViewConstraint.constant = inistalMoveValue + + self.view.bringSubviewToFront(topNavView) + + topNavView.backgroundColor = UIColor.white + topNavView.alpha = 0 + } + override func viewDidDisappear(_ animated: Bool) { + + } +} + +extension SlideViewController : FloatingPanelControllerDelegate{ + func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? { + return FloatingPanelHotelLayout(middle: self.view.frame.height/2) + } + + func floatingPanel(_ vc: FloatingPanelController, behaviorFor newCollection: UITraitCollection) -> FloatingPanelBehavior? { + return nil + } + func floatingPanelDidEndDecelerating(_ vc: FloatingPanelController) { + var animator: UIViewPropertyAnimator! + animator = UIViewPropertyAnimator(duration: 0, curve: .linear) { [unowned self] in + let visibleHeight = vc.surfaceView.frame.height - vc.surfaceView.frame.minY + self.inistalMoveValue = vc.surfaceView.frame.minY - (visibleHeight/2) + self.imageViewTopSuperViewConstraint.constant = max(self.inistalMoveValue,0) + } + animator.startAnimation() +// UIView.animate(withDuration: 0.25) { +// let visibleHeight = vc.surfaceView.frame.height - vc.surfaceView.frame.minY +// self.inistalMoveValue = vc.surfaceView.frame.minY - (visibleHeight/2) +// self.imageViewTopSuperViewConstraint.constant = max(self.inistalMoveValue,0) +// } + } + func floatingPanelDidChangePosition(_ vc: FloatingPanelController) { + +// UIView.animate(withDuration: 0.25) { +// +// self.middleImageView.layoutIfNeeded() +// } +// +// var animator: UIViewPropertyAnimator! +// animator.addCompletion({ +// +// }) +// animator = UIViewPropertyAnimator(duration: 0.25, curve: .easeInOut) { [unowned self] in +// let visibleHeight = vc.surfaceView.frame.height - vc.surfaceView.frame.minY +// self.inistalMoveValue = vc.surfaceView.frame.minY - (visibleHeight/2) +// self.imageViewTopSuperViewConstraint.constant = max(self.inistalMoveValue,0) +// } +// animator.startAnimation() +// DispatchQueue.main.asyncAfter(deadline: .now() + 0.25, execute: { +// let visibleHeight = vc.surfaceView.frame.height - vc.surfaceView.frame.minY +// self.inistalMoveValue = vc.surfaceView.frame.minY - (visibleHeight/2) +// self.imageViewTopSuperViewConstraint.constant = max(self.inistalMoveValue,0) +// }) + } + func floatingPanelWillBeginDragging(_ vc: FloatingPanelController) { + if vc.position == .full { + // Dimiss top bar with dissolve animation + UIView.animate(withDuration: 0.25) { + self.view.backgroundColor = self.initialColor + } + } + + +// NSLayoutConstraint.deactivate([imageViewTopSuperViewConstraint] + [imageViewTopConstraint]) +// if (middleImageView.frame.minY > 0){ +// let visibleHeight = vc.surfaceView.frame.height - vc.surfaceView.frame.minY +// inistalMoveValue = visibleHeight/2 +// imageViewTopConstraint.constant = -inistalMoveValue +// NSLayoutConstraint.activate([imageViewTopConstraint]) +// }else{ +// NSLayoutConstraint.activate([imageViewTopSuperViewConstraint]) +// } +// let move = vc.panGestureRecognizer.translation(in: self.view); +// debugPrint("move",move) +// imageViewTopConstraint.constant = inistalMoveValue + } + func floatingPanelDidEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetPosition: FloatingPanelPosition) { + if targetPosition == .full { + // Present top bar with dissolve animation + UIView.animate(withDuration: 0.25) { + self.view.backgroundColor = .white + } + } +// let visibleHeight = vc.surfaceView.frame.height - vc.surfaceView.frame.minY +// inistalMoveValue = vc.surfaceView.frame.minY - (visibleHeight/2) +// imageViewTopSuperViewConstraint.constant = max(inistalMoveValue,0) + +// NSLayoutConstraint.deactivate([imageViewTopSuperViewConstraint] + [imageViewTopConstraint]) +// if (middleImageView.frame.minY > 0){ +// let visibleHeight = vc.surfaceView.frame.height - vc.surfaceView.frame.minY +// inistalMoveValue = visibleHeight/2 +// imageViewTopConstraint.constant = -inistalMoveValue +// NSLayoutConstraint.activate([imageViewTopConstraint]) +// }else{ +// NSLayoutConstraint.activate([imageViewTopSuperViewConstraint]) +// } +// let tran = vc.panGestureRecognizer.translation(in: self.view) +// UIView.animate(withDuration: 0.25) { +// self.imageViewTopConstraint.constant = tran.y +// } +// let move = vc.panGestureRecognizer.translation(in: self.view); +// debugPrint("move",move) +// imageViewTopConstraint.constant = inistalMoveValue + move.y +// inistalMoveValue = inistalMoveValue + move.y + } + func floatingPanelDidMove(_ vc: FloatingPanelController) { +// if vc.surfaceView.frame.minY > vc.originYOfSurface(for: .half) { +// let progress = vc.surfaceView.frame.minY + let progress = (vc.surfaceView.frame.minY - vc.originYOfSurface(for: .tip)) / (vc.originYOfSurface(for: .tip) - vc.originYOfSurface(for: .tip)) +// imageViewTopConstraint.constant = max(min(progress, 1.0), 0.0) * 17 +// } else { +// imageViewTopConstraint.constant = -100 +// } +// let tran = vc.panGestureRecognizer.translation(in: self.view) +// UIView.animate(withDuration: 0.25) { +// self.imageViewTopConstraint.constant = tran.y +// } + +// let move = vc.panGestureRecognizer.translation(in: self.view); +// if(move.y > 0){ +// inistalMoveValue = (move.y + inistalMoveValue) +// }else{ +// inistalMoveValue = (move.y - inistalMoveValue) +// } +// NSLayoutConstraint.deactivate([imageViewTopSuperViewConstraint] + [imageViewTopConstraint]) +// if (middleImageView.frame.minY > 0){ +// let visibleHeight = vc.surfaceView.frame.height - vc.surfaceView.frame.minY +// inistalMoveValue = visibleHeight/2 +// imageViewTopConstraint.constant = -inistalMoveValue +// NSLayoutConstraint.activate([imageViewTopConstraint]) +// }else{ +// NSLayoutConstraint.activate([imageViewTopSuperViewConstraint]) +// } +// if (move.y > 0){ +// // Moving dwon +// +// }else{ +// // Moving UP +// } + +// if (middleImageView.frame.minY > 0){ + let visibleHeight = vc.surfaceView.frame.height - vc.surfaceView.frame.minY + inistalMoveValue = vc.surfaceView.frame.minY - (visibleHeight/2) + imageViewTopSuperViewConstraint.constant = max(inistalMoveValue,0) +// }else{ +// +// } + + let percentage:CGFloat = vc.surfaceView.frame.minY/self.view.frame.size.height + topNavView.alpha = 1.0 - percentage + } +} diff --git a/Examples/MiddlePanel/MiddlePanel/ViewController.swift b/Examples/MiddlePanel/MiddlePanel/ViewController.swift new file mode 100644 index 00000000..4edd21e9 --- /dev/null +++ b/Examples/MiddlePanel/MiddlePanel/ViewController.swift @@ -0,0 +1,279 @@ +// +// ViewController.swift +// MiddlePanel +// +// Created by Ramesh R C on 11.12.19. +// Copyright © 2019 Ramesh R C. All rights reserved. +// + +import UIKit +import FloatingPanel + +class ViewController: UIViewController { + + var fpc: FloatingPanelController! + var hotelDetailsVC: HotelDetailsViewController! + var initialColor: UIColor = .black + @IBOutlet var middleImageView: UIView! + @IBOutlet var topNavView: UIView! + var imageViewTopConstraint: NSLayoutConstraint! + var imageViewTopSuperViewConstraint: NSLayoutConstraint! + var inistalMoveValue:CGFloat = -100 + let ppp = FloatingPanelHotelBehavior() + + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + + imageViewTopSuperViewConstraint = middleImageView.topAnchor.constraint(equalTo: view.topAnchor, constant:0) + initialColor = view.backgroundColor! + // Initialize FloatingPanelController + fpc = FloatingPanelController() + fpc.delegate = self + + // Initialize FloatingPanelController and add the view + fpc.surfaceView.backgroundColor = UIColor(displayP3Red: 30.0/255.0, green: 30.0/255.0, blue: 30.0/255.0, alpha: 1.0) + fpc.surfaceView.cornerRadius = 24.0 + fpc.surfaceView.shadowHidden = true + fpc.surfaceView.borderWidth = 1.0 / traitCollection.displayScale + fpc.surfaceView.borderColor = UIColor.black.withAlphaComponent(0.2) + + hotelDetailsVC = storyboard?.instantiateViewController(withIdentifier: "HotelDetailsVC") as? HotelDetailsViewController + + // Set a content view controller + fpc.set(contentViewController: hotelDetailsVC) +// fpc.track(scrollView: newsVC.scrollView) + fpc.addPanel(toParent: self, belowView: nil, animated: false) + + middleImageView.frame = .zero + fpc.view.addSubview(middleImageView) +// fpc.surfaceView.insertSubview(middleImageView, belowSubview: fpc.surfaceView) + middleImageView.translatesAutoresizingMaskIntoConstraints = false + imageViewTopConstraint = middleImageView.topAnchor.constraint(equalTo: fpc.surfaceView.topAnchor, constant: inistalMoveValue) + + NSLayoutConstraint.activate([ + imageViewTopSuperViewConstraint, + middleImageView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 0.0), + middleImageView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: 0.0), + middleImageView.heightAnchor.constraint(equalToConstant: 200) + ]) + fpc.view.bringSubviewToFront(fpc.surfaceView) + + } + override func viewDidAppear(_ animated: Bool) { + let visibleHeight = fpc.surfaceView.frame.height - fpc.surfaceView.frame.minY + inistalMoveValue = fpc.surfaceView.frame.minY - (visibleHeight/2) + imageViewTopSuperViewConstraint.constant = inistalMoveValue + + self.view.bringSubviewToFront(topNavView) + + topNavView.backgroundColor = UIColor.white + topNavView.alpha = 0 + } + override func viewDidDisappear(_ animated: Bool) { + + } +} + +extension ViewController : FloatingPanelControllerDelegate{ + func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? { + return FloatingPanelHotelLayout(middle: self.view.frame.height/2) + } + + func floatingPanel(_ vc: FloatingPanelController, behaviorFor newCollection: UITraitCollection) -> FloatingPanelBehavior? { + return nil + } + func floatingPanelDidEndDecelerating(_ vc: FloatingPanelController) { + var animator: UIViewPropertyAnimator! + animator = UIViewPropertyAnimator(duration: 0, curve: .linear) { [unowned self] in + let visibleHeight = vc.surfaceView.frame.height - vc.surfaceView.frame.minY + self.inistalMoveValue = vc.surfaceView.frame.minY - (visibleHeight/2) + self.imageViewTopSuperViewConstraint.constant = max(self.inistalMoveValue,0) + } + animator.startAnimation() +// UIView.animate(withDuration: 0.25) { +// let visibleHeight = vc.surfaceView.frame.height - vc.surfaceView.frame.minY +// self.inistalMoveValue = vc.surfaceView.frame.minY - (visibleHeight/2) +// self.imageViewTopSuperViewConstraint.constant = max(self.inistalMoveValue,0) +// } + } + func floatingPanelDidChangePosition(_ vc: FloatingPanelController) { + +// UIView.animate(withDuration: 0.25) { +// +// self.middleImageView.layoutIfNeeded() +// } +// +// var animator: UIViewPropertyAnimator! +// animator.addCompletion({ +// +// }) +// animator = UIViewPropertyAnimator(duration: 0.25, curve: .easeInOut) { [unowned self] in +// let visibleHeight = vc.surfaceView.frame.height - vc.surfaceView.frame.minY +// self.inistalMoveValue = vc.surfaceView.frame.minY - (visibleHeight/2) +// self.imageViewTopSuperViewConstraint.constant = max(self.inistalMoveValue,0) +// } +// animator.startAnimation() +// DispatchQueue.main.asyncAfter(deadline: .now() + 0.25, execute: { +// let visibleHeight = vc.surfaceView.frame.height - vc.surfaceView.frame.minY +// self.inistalMoveValue = vc.surfaceView.frame.minY - (visibleHeight/2) +// self.imageViewTopSuperViewConstraint.constant = max(self.inistalMoveValue,0) +// }) + } + func floatingPanelWillBeginDragging(_ vc: FloatingPanelController) { + if vc.position == .full { + // Dimiss top bar with dissolve animation + UIView.animate(withDuration: 0.25) { + self.view.backgroundColor = self.initialColor + } + } + + +// NSLayoutConstraint.deactivate([imageViewTopSuperViewConstraint] + [imageViewTopConstraint]) +// if (middleImageView.frame.minY > 0){ +// let visibleHeight = vc.surfaceView.frame.height - vc.surfaceView.frame.minY +// inistalMoveValue = visibleHeight/2 +// imageViewTopConstraint.constant = -inistalMoveValue +// NSLayoutConstraint.activate([imageViewTopConstraint]) +// }else{ +// NSLayoutConstraint.activate([imageViewTopSuperViewConstraint]) +// } +// let move = vc.panGestureRecognizer.translation(in: self.view); +// debugPrint("move",move) +// imageViewTopConstraint.constant = inistalMoveValue + } + func floatingPanelDidEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetPosition: FloatingPanelPosition) { + if targetPosition == .full { + // Present top bar with dissolve animation + UIView.animate(withDuration: 0.25) { + self.view.backgroundColor = .white + } + } +// let visibleHeight = vc.surfaceView.frame.height - vc.surfaceView.frame.minY +// inistalMoveValue = vc.surfaceView.frame.minY - (visibleHeight/2) +// imageViewTopSuperViewConstraint.constant = max(inistalMoveValue,0) + +// NSLayoutConstraint.deactivate([imageViewTopSuperViewConstraint] + [imageViewTopConstraint]) +// if (middleImageView.frame.minY > 0){ +// let visibleHeight = vc.surfaceView.frame.height - vc.surfaceView.frame.minY +// inistalMoveValue = visibleHeight/2 +// imageViewTopConstraint.constant = -inistalMoveValue +// NSLayoutConstraint.activate([imageViewTopConstraint]) +// }else{ +// NSLayoutConstraint.activate([imageViewTopSuperViewConstraint]) +// } +// let tran = vc.panGestureRecognizer.translation(in: self.view) +// UIView.animate(withDuration: 0.25) { +// self.imageViewTopConstraint.constant = tran.y +// } +// let move = vc.panGestureRecognizer.translation(in: self.view); +// debugPrint("move",move) +// imageViewTopConstraint.constant = inistalMoveValue + move.y +// inistalMoveValue = inistalMoveValue + move.y + } + func floatingPanelDidMove(_ vc: FloatingPanelController) { +// if vc.surfaceView.frame.minY > vc.originYOfSurface(for: .half) { +// let progress = vc.surfaceView.frame.minY + let progress = (vc.surfaceView.frame.minY - vc.originYOfSurface(for: .tip)) / (vc.originYOfSurface(for: .tip) - vc.originYOfSurface(for: .tip)) +// imageViewTopConstraint.constant = max(min(progress, 1.0), 0.0) * 17 +// } else { +// imageViewTopConstraint.constant = -100 +// } +// let tran = vc.panGestureRecognizer.translation(in: self.view) +// UIView.animate(withDuration: 0.25) { +// self.imageViewTopConstraint.constant = tran.y +// } + +// let move = vc.panGestureRecognizer.translation(in: self.view); +// if(move.y > 0){ +// inistalMoveValue = (move.y + inistalMoveValue) +// }else{ +// inistalMoveValue = (move.y - inistalMoveValue) +// } +// NSLayoutConstraint.deactivate([imageViewTopSuperViewConstraint] + [imageViewTopConstraint]) +// if (middleImageView.frame.minY > 0){ +// let visibleHeight = vc.surfaceView.frame.height - vc.surfaceView.frame.minY +// inistalMoveValue = visibleHeight/2 +// imageViewTopConstraint.constant = -inistalMoveValue +// NSLayoutConstraint.activate([imageViewTopConstraint]) +// }else{ +// NSLayoutConstraint.activate([imageViewTopSuperViewConstraint]) +// } +// if (move.y > 0){ +// // Moving dwon +// +// }else{ +// // Moving UP +// } + +// if (middleImageView.frame.minY > 0){ + let visibleHeight = vc.surfaceView.frame.height - vc.surfaceView.frame.minY + inistalMoveValue = vc.surfaceView.frame.minY - (visibleHeight/2) + imageViewTopSuperViewConstraint.constant = max(inistalMoveValue,0) +// }else{ +// +// } + + let percentage:CGFloat = vc.surfaceView.frame.minY/self.view.frame.size.height + topNavView.alpha = 1.0 - percentage + } +} + + +class FloatingPanelHotelLayout: FloatingPanelLayout { + var initialPosition: FloatingPanelPosition { + return .tip + } + + var topInteractionBuffer: CGFloat { return 0.0 } + var bottomInteractionBuffer: CGFloat { return 0.0 } + var supportedPositions: Set { + return [.full, .half] + } + var mm:CGFloat = 262.0 + init(middle:CGFloat) { + mm = middle + } + func insetFor(position: FloatingPanelPosition) -> CGFloat? { + switch position { + case .full: return 0.0 + case .half: return mm + default: return nil + } + } + + func backdropAlphaFor(position: FloatingPanelPosition) -> CGFloat { + return 0.0 + } +} + + +class FloatingPanelHotelBehavior: FloatingPanelBehavior { + var velocityThreshold: CGFloat { + return 15.0 + } + + func interactionAnimator(_ fpc: FloatingPanelController, to targetPosition: FloatingPanelPosition, with velocity: CGVector) -> UIViewPropertyAnimator { + let timing = timeingCurve(to: targetPosition, with: velocity) + return UIViewPropertyAnimator(duration: 0, timingParameters: timing) + } + + private func timeingCurve(to: FloatingPanelPosition, with velocity: CGVector) -> UITimingCurveProvider { + let damping = self.damping(with: velocity) + return UISpringTimingParameters(dampingRatio: damping, + frequencyResponse: 0.4, + initialVelocity: velocity) + } + + private func damping(with velocity: CGVector) -> CGFloat { + switch velocity.dy { + case ...(-velocityThreshold): + return 0.7 + case velocityThreshold...: + return 0.7 + default: + return 1.0 + } + } +} diff --git a/Examples/MiddlePanel/MiddlePanelTests/Info.plist b/Examples/MiddlePanel/MiddlePanelTests/Info.plist new file mode 100644 index 00000000..64d65ca4 --- /dev/null +++ b/Examples/MiddlePanel/MiddlePanelTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Examples/MiddlePanel/MiddlePanelTests/MiddlePanelTests.swift b/Examples/MiddlePanel/MiddlePanelTests/MiddlePanelTests.swift new file mode 100644 index 00000000..f18943ae --- /dev/null +++ b/Examples/MiddlePanel/MiddlePanelTests/MiddlePanelTests.swift @@ -0,0 +1,34 @@ +// +// MiddlePanelTests.swift +// MiddlePanelTests +// +// Created by Ramesh R C on 11.12.19. +// Copyright © 2019 Ramesh R C. All rights reserved. +// + +import XCTest +@testable import MiddlePanel + +class MiddlePanelTests: XCTestCase { + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/Examples/MiddlePanel/MiddlePanelUITests/Info.plist b/Examples/MiddlePanel/MiddlePanelUITests/Info.plist new file mode 100644 index 00000000..64d65ca4 --- /dev/null +++ b/Examples/MiddlePanel/MiddlePanelUITests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Examples/MiddlePanel/MiddlePanelUITests/MiddlePanelUITests.swift b/Examples/MiddlePanel/MiddlePanelUITests/MiddlePanelUITests.swift new file mode 100644 index 00000000..fddb6e7b --- /dev/null +++ b/Examples/MiddlePanel/MiddlePanelUITests/MiddlePanelUITests.swift @@ -0,0 +1,43 @@ +// +// MiddlePanelUITests.swift +// MiddlePanelUITests +// +// Created by Ramesh R C on 11.12.19. +// Copyright © 2019 Ramesh R C. All rights reserved. +// + +import XCTest + +class MiddlePanelUITests: XCTestCase { + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + // UI tests must launch the application that they test. + let app = XCUIApplication() + app.launch() + + // Use recording to get started writing UI tests. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testLaunchPerformance() { + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) { + // This measures how long it takes to launch your application. + measure(metrics: [XCTOSSignpostMetric.applicationLaunch]) { + XCUIApplication().launch() + } + } + } +} diff --git a/Examples/Samples/Sources/Info.plist b/Examples/Samples/Sources/Info.plist index 16be3b68..ba5bc223 100644 --- a/Examples/Samples/Sources/Info.plist +++ b/Examples/Samples/Sources/Info.plist @@ -17,7 +17,7 @@ CFBundleShortVersionString 1.0 CFBundleVersion - 1 + middle-panel(92fc062) LSRequiresIPhoneOS UILaunchStoryboardName diff --git a/FloatingPanel.xcodeproj/project.pbxproj b/FloatingPanel.xcodeproj/project.pbxproj index b6417dae..808a7c28 100644 --- a/FloatingPanel.xcodeproj/project.pbxproj +++ b/FloatingPanel.xcodeproj/project.pbxproj @@ -224,7 +224,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1010; - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1110; ORGANIZATIONNAME = scenee; TargetAttributes = { 545DB9C02151169500CA77B8 = { @@ -472,7 +472,6 @@ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/FloatingPanel.xcodeproj/xcshareddata/xcschemes/FloatingPanel.xcscheme b/FloatingPanel.xcodeproj/xcshareddata/xcschemes/FloatingPanel.xcscheme index 3d419070..94c0ba9b 100644 --- a/FloatingPanel.xcodeproj/xcshareddata/xcschemes/FloatingPanel.xcscheme +++ b/FloatingPanel.xcodeproj/xcshareddata/xcschemes/FloatingPanel.xcscheme @@ -1,6 +1,6 @@ + + + + + + diff --git a/Framework/Sources/FloatingPanelController.swift b/Framework/Sources/FloatingPanelController.swift new file mode 100644 index 00000000..64cd3cbe --- /dev/null +++ b/Framework/Sources/FloatingPanelController.swift @@ -0,0 +1,636 @@ +// +// Created by Shin Yamamoto on 2018/09/18. +// Copyright © 2018 Shin Yamamoto. All rights reserved. +// + +import UIKit + +public protocol FloatingPanelControllerDelegate: class { + // if it returns nil, FloatingPanelController uses the default layout + func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? + + // if it returns nil, FloatingPanelController uses the default behavior + func floatingPanel(_ vc: FloatingPanelController, behaviorFor newCollection: UITraitCollection) -> FloatingPanelBehavior? + + /// Called when the floating panel has changed to a new position. Can be called inside an animation block, so any + /// view properties set inside this function will be automatically animated alongside the panel. + func floatingPanelDidChangePosition(_ vc: FloatingPanelController) + + /// Asks the delegate if dragging should begin by the pan gesture recognizer. + func floatingPanelShouldBeginDragging(_ vc: FloatingPanelController) -> Bool + + func floatingPanelDidMove(_ vc: FloatingPanelController) // any surface frame changes in dragging + + // called on start of dragging (may require some time and or distance to move) + func floatingPanelWillBeginDragging(_ vc: FloatingPanelController) + // called on finger up if the user dragged. velocity is in points/second. + func floatingPanelDidEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetPosition: FloatingPanelPosition) + func floatingPanelWillBeginDecelerating(_ vc: FloatingPanelController) // called on finger up as we are moving + func floatingPanelDidEndDecelerating(_ vc: FloatingPanelController) // called when scroll view grinds to a halt + + // called on start of dragging to remove its views from a parent view controller + func floatingPanelDidEndDraggingToRemove(_ vc: FloatingPanelController, withVelocity velocity: CGPoint) + // called when its views are removed from a parent view controller + func floatingPanelDidEndRemove(_ vc: FloatingPanelController) + + /// Asks the delegate if the other gesture recognizer should be allowed to recognize the gesture in parallel. + /// + /// By default, any tap and long gesture recognizers are allowed to recognize gestures simultaneously. + func floatingPanel(_ vc: FloatingPanelController, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool + + func floatingPanelDidViewLayout(_ vc: FloatingPanelController) +} + +public extension FloatingPanelControllerDelegate { + func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? { + return nil + } + func floatingPanel(_ vc: FloatingPanelController, behaviorFor newCollection: UITraitCollection) -> FloatingPanelBehavior? { + return nil + } + func floatingPanelDidChangePosition(_ vc: FloatingPanelController) {} + func floatingPanelShouldBeginDragging(_ vc: FloatingPanelController) -> Bool { + return true + } + func floatingPanelDidMove(_ vc: FloatingPanelController) {} + func floatingPanelWillBeginDragging(_ vc: FloatingPanelController) {} + func floatingPanelDidEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetPosition: FloatingPanelPosition) {} + func floatingPanelWillBeginDecelerating(_ vc: FloatingPanelController) {} + func floatingPanelDidEndDecelerating(_ vc: FloatingPanelController) {} + + func floatingPanelDidEndDraggingToRemove(_ vc: FloatingPanelController, withVelocity velocity: CGPoint) {} + func floatingPanelDidEndRemove(_ vc: FloatingPanelController) {} + + func floatingPanel(_ vc: FloatingPanelController, shouldRecognizeSimultaneouslyWith gestureRecognizer: UIGestureRecognizer) -> Bool { + return false + } + + func floatingPanelDidViewLayout(_ vc: FloatingPanelController){} +} + + +public enum FloatingPanelPosition: Int { + case full + case half + case tip + case hidden + + static var allCases: [FloatingPanelPosition] { + return [.full, .half, .tip, .hidden] + } + + func next(in positions: [FloatingPanelPosition]) -> FloatingPanelPosition { + #if swift(>=4.2) + guard + let index = positions.firstIndex(of: self), + positions.indices.contains(index + 1) + else { return self } + #else + guard + let index = positions.index(of: self), + positions.indices.contains(index + 1) + else { return self } + #endif + return positions[index + 1] + } + + func pre(in positions: [FloatingPanelPosition]) -> FloatingPanelPosition { + #if swift(>=4.2) + guard + let index = positions.firstIndex(of: self), + positions.indices.contains(index - 1) + else { return self } + #else + guard + let index = positions.index(of: self), + positions.indices.contains(index - 1) + else { return self } + #endif + return positions[index - 1] + } +} + +/// +/// A container view controller to display a floating panel to present contents in parallel as a user wants. +/// +open class FloatingPanelController: UIViewController { + /// Constants indicating how safe area insets are added to the adjusted content inset. + public enum ContentInsetAdjustmentBehavior: Int { + case always + case never + } + + /// A flag used to determine how the controller object lays out the content view when the surface position changes. + public enum ContentMode: Int { + /// The option to fix the content to keep the height of the top most position. + case `static` + /// The option to scale the content to fit the bounds of the root view by changing the surface position. + case fitToBounds + } + + /// The delegate of the floating panel controller object. + public weak var delegate: FloatingPanelControllerDelegate?{ + didSet{ + didUpdateDelegate() + } + } + + /// Returns the surface view managed by the controller object. It's the same as `self.view`. + public var surfaceView: FloatingPanelSurfaceView! { + return floatingPanel.surfaceView + } + + /// Returns the backdrop view managed by the controller object. + public var backdropView: FloatingPanelBackdropView! { + return floatingPanel.backdropView + } + + /// Returns the scroll view that the controller tracks. + public weak var scrollView: UIScrollView? { + return floatingPanel.scrollView + } + + // The underlying gesture recognizer for pan gestures + public var panGestureRecognizer: UIPanGestureRecognizer { + return floatingPanel.panGestureRecognizer + } + + /// The current position of the floating panel controller's contents. + public var position: FloatingPanelPosition { + return floatingPanel.state + } + + /// The layout object managed by the controller + public var layout: FloatingPanelLayout { + return floatingPanel.layoutAdapter.layout + } + + /// The behavior object managed by the controller + public var behavior: FloatingPanelBehavior { + return floatingPanel.behavior + } + + /// The content insets of the tracking scroll view derived from this safe area + public var adjustedContentInsets: UIEdgeInsets { + return floatingPanel.layoutAdapter.adjustedContentInsets + } + + /// The behavior for determining the adjusted content offsets. + /// + /// This property specifies how the content area of the tracking scroll view is modified using `adjustedContentInsets`. The default value of this property is FloatingPanelController.ContentInsetAdjustmentBehavior.always. + public var contentInsetAdjustmentBehavior: ContentInsetAdjustmentBehavior = .always + + /// A Boolean value that determines whether the removal interaction is enabled. + public var isRemovalInteractionEnabled: Bool { + set { floatingPanel.isRemovalInteractionEnabled = newValue } + get { return floatingPanel.isRemovalInteractionEnabled } + } + + /// The view controller responsible for the content portion of the floating panel. + public var contentViewController: UIViewController? { + set { set(contentViewController: newValue) } + get { return _contentViewController } + } + + /// The NearbyPosition determines that finger's nearby position. + public var nearbyPosition: FloatingPanelPosition { + let currentY = surfaceView.frame.minY + return floatingPanel.targetPosition(from: currentY, with: .zero) + } + + public var contentMode: ContentMode = .static { + didSet { + guard position != .hidden else { return } + activateLayout() + } + } + + private var _contentViewController: UIViewController? + + private(set) var floatingPanel: FloatingPanelCore! + private var preSafeAreaInsets: UIEdgeInsets = .zero // Capture the latest one + private var safeAreaInsetsObservation: NSKeyValueObservation? + private let modalTransition = FloatingPanelModalTransition() + + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setUp() + } + + /// Initialize a newly created floating panel controller. + public init(delegate: FloatingPanelControllerDelegate? = nil) { + super.init(nibName: nil, bundle: nil) + self.delegate = delegate + setUp() + } + + private func setUp() { + _ = FloatingPanelController.dismissSwizzling + + modalPresentationStyle = .custom + transitioningDelegate = modalTransition + + floatingPanel = FloatingPanelCore(self, + layout: fetchLayout(for: self.traitCollection), + behavior: fetchBehavior(for: self.traitCollection)) + } + + private func didUpdateDelegate(){ + floatingPanel.layoutAdapter.layout = fetchLayout(for: traitCollection) + floatingPanel.behavior = fetchBehavior(for: self.traitCollection) + } + + // MARK:- Overrides + + /// Creates the view that the controller manages. + open override func loadView() { + assert(self.storyboard == nil, "Storyboard isn't supported") + + let view = FloatingPanelPassThroughView() + view.backgroundColor = .clear + + backdropView.frame = view.bounds + view.addSubview(backdropView) + + surfaceView.frame = view.bounds + view.addSubview(surfaceView) + + self.view = view as UIView + } + + open override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + if #available(iOS 11.0, *) {} + else { + // Because {top,bottom}LayoutGuide is managed as a view + if preSafeAreaInsets != layoutInsets, + floatingPanel.isDecelerating == false { + self.update(safeAreaInsets: layoutInsets) + } + } + delegate?.floatingPanelDidViewLayout(self) + } + + open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + super.viewWillTransition(to: size, with: coordinator) + + if view.translatesAutoresizingMaskIntoConstraints { + view.frame.size = size + view.layoutIfNeeded() + } + } + + open override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) { + super.willTransition(to: newCollection, with: coordinator) + self.prepare(for: newCollection) + } + + open override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + safeAreaInsetsObservation = nil + } + + // MARK:- Internals + func prepare(for newCollection: UITraitCollection) { + guard newCollection.shouldUpdateLayout(from: traitCollection) else { return } + // Change a layout & behavior for a new trait collection + reloadLayout(for: newCollection) + activateLayout() + floatingPanel.behavior = fetchBehavior(for: newCollection) + } + + // MARK:- Privates + + private func fetchLayout(for traitCollection: UITraitCollection) -> FloatingPanelLayout { + switch traitCollection.verticalSizeClass { + case .compact: + return self.delegate?.floatingPanel(self, layoutFor: traitCollection) ?? FloatingPanelDefaultLandscapeLayout() + default: + return self.delegate?.floatingPanel(self, layoutFor: traitCollection) ?? FloatingPanelDefaultLayout() + } + } + + private func fetchBehavior(for traitCollection: UITraitCollection) -> FloatingPanelBehavior { + return self.delegate?.floatingPanel(self, behaviorFor: traitCollection) ?? FloatingPanelDefaultBehavior() + } + + private func update(safeAreaInsets: UIEdgeInsets) { + guard + preSafeAreaInsets != safeAreaInsets + else { return } + + log.debug("Update safeAreaInsets", safeAreaInsets) + + // Prevent an infinite loop on iOS 10: setUpLayout() -> viewDidLayoutSubviews() -> setUpLayout() + preSafeAreaInsets = safeAreaInsets + + activateLayout() + + switch contentInsetAdjustmentBehavior { + case .always: + scrollView?.contentInset = adjustedContentInsets + scrollView?.scrollIndicatorInsets = adjustedContentInsets + default: + break + } + } + + private func reloadLayout(for traitCollection: UITraitCollection) { + floatingPanel.layoutAdapter.layout = fetchLayout(for: traitCollection) + + if let parent = self.parent { + if let layout = layout as? UIViewController, layout == parent { + log.warning("A memory leak will occur by a retain cycle because \(self) owns the parent view controller(\(parent)) as the layout object. Don't let the parent adopt FloatingPanelLayout.") + } + if let behavior = behavior as? UIViewController, behavior == parent { + log.warning("A memory leak will occur by a retain cycle because \(self) owns the parent view controller(\(parent)) as the behavior object. Don't let the parent adopt FloatingPanelBehavior.") + } + } + } + + private func activateLayout() { + floatingPanel.layoutAdapter.prepareLayout(in: self) + + // preserve the current content offset + let contentOffset = scrollView?.contentOffset + + floatingPanel.layoutAdapter.updateHeight() + floatingPanel.layoutAdapter.activateLayout(of: floatingPanel.state) + + scrollView?.contentOffset = contentOffset ?? .zero + } + + // MARK: - Container view controller interface + + /// Shows the surface view at the initial position defined by the current layout + public func show(animated: Bool = false, completion: (() -> Void)? = nil) { + // Must apply the current layout here + reloadLayout(for: traitCollection) + activateLayout() + + if #available(iOS 11.0, *) { + // Must track the safeAreaInsets of `self.view` to update the layout. + // There are 2 reasons. + // 1. This or the parent VC doesn't call viewSafeAreaInsetsDidChange() on the bottom + // inset's update expectedly. + // 2. The safe area top inset can be variable on the large title navigation bar(iOS11+). + // That's why it needs the observation to keep `adjustedContentInsets` correct. + safeAreaInsetsObservation = self.observe(\.view.safeAreaInsets, options: [.initial, .new, .old]) { [weak self] (vc, change) in + guard change.oldValue != change.newValue else { return } + self?.update(safeAreaInsets: vc.layoutInsets) + } + } else { + // KVOs for topLayoutGuide & bottomLayoutGuide are not effective. + // Instead, update(safeAreaInsets:) is called at `viewDidLayoutSubviews()` + } + + move(to: floatingPanel.layoutAdapter.layout.initialPosition, + animated: animated, + completion: completion) + } + + /// Hides the surface view to the hidden position + public func hide(animated: Bool = false, completion: (() -> Void)? = nil) { + move(to: .hidden, + animated: animated, + completion: completion) + } + + /// Adds the view managed by the controller as a child of the specified view controller. + /// - Parameters: + /// - parent: A parent view controller object that displays FloatingPanelController's view. A container view controller object isn't applicable. + /// - belowView: Insert the surface view managed by the controller below the specified view. By default, the surface view will be added to the end of the parent list of subviews. + /// - animated: Pass true to animate the presentation; otherwise, pass false. + public func addPanel(toParent parent: UIViewController, belowView: UIView? = nil, animated: Bool = false) { + guard self.parent == nil else { + log.warning("Already added to a parent(\(parent))") + return + } + precondition((parent is UINavigationController) == false, "UINavigationController displays only one child view controller at a time.") + precondition((parent is UITabBarController) == false, "UITabBarController displays child view controllers with a radio-style selection interface") + precondition((parent is UISplitViewController) == false, "UISplitViewController manages two child view controllers in a master-detail interface") + precondition((parent is UITableViewController) == false, "UITableViewController should not be the parent because the view is a table view so that a floating panel doens't work well") + precondition((parent is UICollectionViewController) == false, "UICollectionViewController should not be the parent because the view is a collection view so that a floating panel doens't work well") + + if let belowView = belowView { + parent.view.insertSubview(self.view, belowSubview: belowView) + } else { + parent.view.addSubview(self.view) + } + + #if swift(>=4.2) + parent.addChild(self) + #else + parent.addChildViewController(self) + #endif + + view.frame = parent.view.bounds // Needed for a correct safe area configuration + view.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + self.view.topAnchor.constraint(equalTo: parent.view.topAnchor, constant: 0.0), + self.view.leftAnchor.constraint(equalTo: parent.view.leftAnchor, constant: 0.0), + self.view.rightAnchor.constraint(equalTo: parent.view.rightAnchor, constant: 0.0), + self.view.bottomAnchor.constraint(equalTo: parent.view.bottomAnchor, constant: 0.0), + ]) + + show(animated: animated) { [weak self] in + guard let `self` = self else { return } + #if swift(>=4.2) + self.didMove(toParent: parent) + #else + self.didMove(toParentViewController: parent) + #endif + } + } + + /// Removes the controller and the managed view from its parent view controller + /// - Parameters: + /// - animated: Pass true to animate the presentation; otherwise, pass false. + /// - completion: The block to execute after the view controller is dismissed. This block has no return value and takes no parameters. You may specify nil for this parameter. + public func removePanelFromParent(animated: Bool, completion: (() -> Void)? = nil) { + guard self.parent != nil else { + completion?() + return + } + + hide(animated: animated) { [weak self] in + guard let `self` = self else { return } + #if swift(>=4.2) + self.willMove(toParent: nil) + #else + self.willMove(toParentViewController: nil) + #endif + + self.view.removeFromSuperview() + + #if swift(>=4.2) + self.removeFromParent() + #else + self.removeFromParentViewController() + #endif + + completion?() + } + } + + /// Moves the position to the specified position. + /// - Parameters: + /// - to: Pass a FloatingPanelPosition value to move the surface view to the position. + /// - animated: Pass true to animate the presentation; otherwise, pass false. + /// - completion: The block to execute after the view controller has finished moving. This block has no return value and takes no parameters. You may specify nil for this parameter. + public func move(to: FloatingPanelPosition, animated: Bool, completion: (() -> Void)? = nil) { + precondition(floatingPanel.layoutAdapter.vc != nil, "Use show(animated:completion)") + floatingPanel.move(to: to, animated: animated, completion: completion) + } + + /// Sets the view controller responsible for the content portion of the floating panel. + public func set(contentViewController: UIViewController?) { + if let vc = _contentViewController { + #if swift(>=4.2) + vc.willMove(toParent: nil) + #else + vc.willMove(toParentViewController: nil) + #endif + + vc.view.removeFromSuperview() + + #if swift(>=4.2) + vc.removeFromParent() + #else + vc.removeFromParentViewController() + #endif + } + + if let vc = contentViewController { + #if swift(>=4.2) + addChild(vc) + #else + addChildViewController(vc) + #endif + + let surfaceView = floatingPanel.surfaceView + surfaceView.add(contentView: vc.view) + + #if swift(>=4.2) + vc.didMove(toParent: self) + #else + vc.didMove(toParentViewController: self) + #endif + } + + _contentViewController = contentViewController + } + + @available(*, unavailable, renamed: "set(contentViewController:)") + open override func show(_ vc: UIViewController, sender: Any?) { + if let target = self.parent?.targetViewController(forAction: #selector(UIViewController.show(_:sender:)), sender: sender) { + target.show(vc, sender: sender) + } + } + + @available(*, unavailable, renamed: "set(contentViewController:)") + open override func showDetailViewController(_ vc: UIViewController, sender: Any?) { + if let target = self.parent?.targetViewController(forAction: #selector(UIViewController.showDetailViewController(_:sender:)), sender: sender) { + target.showDetailViewController(vc, sender: sender) + } + } + + // MARK: - Scroll view tracking + + /// Tracks the specified scroll view to correspond with the scroll. + /// + /// - Parameters: + /// - scrollView: Specify a scroll view to continuously and seamlessly work in concert with interactions of the surface view or nil to cancel it. + public func track(scrollView: UIScrollView?) { + guard let scrollView = scrollView else { + floatingPanel.scrollView = nil + return + } + + floatingPanel.scrollView = scrollView + + switch contentInsetAdjustmentBehavior { + case .always: + if #available(iOS 11.0, *) { + scrollView.contentInsetAdjustmentBehavior = .never + } else { + #if swift(>=4.2) + children.forEach { (vc) in + vc.automaticallyAdjustsScrollViewInsets = false + } + #else + childViewControllers.forEach { (vc) in + vc.automaticallyAdjustsScrollViewInsets = false + } + #endif + } + default: + break + } + } + + // MARK: - Utilities + + /// Updates the layout object from the delegate and lays out the views managed + /// by the controller immediately. + /// + /// This method updates the `FloatingPanelLayout` object from the delegate and + /// then it calls `layoutIfNeeded()` of the root view to force the view + /// to update the floating panel's layout immediately. It can be called in an + /// animation block. + public func updateLayout() { + reloadLayout(for: traitCollection) + activateLayout() + } + + /// Returns the y-coordinate of the point at the origin of the surface view. + public func originYOfSurface(for pos: FloatingPanelPosition) -> CGFloat { + return floatingPanel.layoutAdapter.positionY(for: pos) + } +} + +extension FloatingPanelController { + private static let dismissSwizzling: Any? = { + let aClass: AnyClass! = UIViewController.self //object_getClass(vc) + if let imp = class_getMethodImplementation(aClass, #selector(dismiss(animated:completion:))), + let originalAltMethod = class_getInstanceMethod(aClass, #selector(fp_original_dismiss(animated:completion:))) { + method_setImplementation(originalAltMethod, imp) + } + let originalMethod = class_getInstanceMethod(aClass, #selector(dismiss(animated:completion:))) + let swizzledMethod = class_getInstanceMethod(aClass, #selector(fp_dismiss(animated:completion:))) + if let originalMethod = originalMethod, let swizzledMethod = swizzledMethod { + // switch implementation.. + method_exchangeImplementations(originalMethod, swizzledMethod) + } + return nil + }() +} + +public extension UIViewController { + @objc func fp_original_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { + // Implementation will be replaced by IMP of self.dismiss(animated:completion:) + } + @objc func fp_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { + // Call dismiss(animated:completion:) to a content view controller + if let fpc = parent as? FloatingPanelController { + if fpc.presentingViewController != nil { + self.fp_original_dismiss(animated: flag, completion: completion) + } else { + fpc.removePanelFromParent(animated: flag, completion: completion) + } + return + } + // Call dismiss(animated:completion:) to FloatingPanelController directly + if let fpc = self as? FloatingPanelController { + if fpc.presentingViewController != nil { + self.fp_original_dismiss(animated: flag, completion: completion) + } else { + fpc.removePanelFromParent(animated: flag, completion: completion) + } + return + } + + // For other view controllers + self.fp_original_dismiss(animated: flag, completion: completion) + } +} diff --git a/Framework/Sources/FloatingPanelLayout.swift b/Framework/Sources/FloatingPanelLayout.swift new file mode 100644 index 00000000..b6c7cadb --- /dev/null +++ b/Framework/Sources/FloatingPanelLayout.swift @@ -0,0 +1,616 @@ +// +// Created by Shin Yamamoto on 2018/09/27. +// Copyright © 2018 Shin Yamamoto. All rights reserved. +// + +import UIKit + +/// FloatingPanelFullScreenLayout +/// +/// Use the layout protocol if you configure full, half and tip insets from the superview, not the safe area. +/// It can't be used with FloatingPanelIntrinsicLayout. +public protocol FloatingPanelFullScreenLayout: FloatingPanelLayout { } + +public extension FloatingPanelFullScreenLayout { + var positionReference: FloatingPanelLayoutReference { + return .fromSuperview + } +} + +/// FloatingPanelIntrinsicLayout +/// +/// Use the layout protocol if you want to layout a panel using the intrinsic height. +/// It can't be used with `FloatingPanelFullScreenLayout`. +/// +/// - Attention: +/// `insetFor(position:)` must return `nil` for the full position. Because +/// the inset is determined automatically by the intrinsic height. +/// You can customize insets only for the half, tip and hidden positions. +/// +/// - Note: +/// By default, the `positionReference` is set to `.fromSafeArea`. +public protocol FloatingPanelIntrinsicLayout: FloatingPanelLayout { } + +public extension FloatingPanelIntrinsicLayout { + var initialPosition: FloatingPanelPosition { + return .full + } + + var supportedPositions: Set { + return [.full] + } + + func insetFor(position: FloatingPanelPosition) -> CGFloat? { + return nil + } + + var positionReference: FloatingPanelLayoutReference { + return .fromSafeArea + } +} + +public enum FloatingPanelLayoutReference: Int { + case fromSafeArea = 0 + case fromSuperview = 1 +} + +public protocol FloatingPanelLayout: class { + /// Returns the initial position of a floating panel. + var initialPosition: FloatingPanelPosition { get } + + /// Returns a set of FloatingPanelPosition objects to tell the applicable + /// positions of the floating panel controller. + /// + /// By default, it returns full, half and tip positions. + var supportedPositions: Set { get } + + /// Return the interaction buffer to the top from the top position. Default is 6.0. + var topInteractionBuffer: CGFloat { get } + + /// Return the interaction buffer to the bottom from the bottom position. Default is 6.0. + /// + /// - Important: + /// The specified buffer is ignored when `FloatingPanelController.isRemovalInteractionEnabled` is set to true. + var bottomInteractionBuffer: CGFloat { get } + + /// Returns a CGFloat value to determine a Y coordinate of a floating panel for each position(full, half, tip and hidden). + /// + /// Its returning value indicates a different inset for each position. + /// For full position, a top inset from a safe area in `FloatingPanelController.view`. + /// For half or tip position, a bottom inset from the safe area. + /// For hidden position, a bottom inset from `FloatingPanelController.view`. + /// If a position isn't supported or the default value is used, return nil. + func insetFor(position: FloatingPanelPosition) -> CGFloat? + + /// Returns X-axis and width layout constraints of the surface view of a floating panel. + /// You must not include any Y-axis and height layout constraints of the surface view + /// because their constraints will be configured by the floating panel controller. + /// By default, the width of a surface view fits a safe area. + func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] + + /// Returns a CGFloat value to determine the backdrop view's alpha for a position. + /// + /// Default is 0.3 at full position, otherwise 0.0. + func backdropAlphaFor(position: FloatingPanelPosition) -> CGFloat + + var positionReference: FloatingPanelLayoutReference { get } + +} + +public extension FloatingPanelLayout { + var topInteractionBuffer: CGFloat { return 6.0 } + var bottomInteractionBuffer: CGFloat { return 6.0 } + + var supportedPositions: Set { + return Set([.full, .half, .tip]) + } + + func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] { + return [ + surfaceView.leftAnchor.constraint(equalTo: view.sideLayoutGuide.leftAnchor, constant: 0.0), + surfaceView.rightAnchor.constraint(equalTo: view.sideLayoutGuide.rightAnchor, constant: 0.0), + ] + } + + func backdropAlphaFor(position: FloatingPanelPosition) -> CGFloat { + return position == .full ? 0.3 : 0.0 + } + + var positionReference: FloatingPanelLayoutReference { + return .fromSafeArea + } +} + +public class FloatingPanelDefaultLayout: FloatingPanelLayout { + public init() { } + + public var initialPosition: FloatingPanelPosition { + return .half + } + + public func insetFor(position: FloatingPanelPosition) -> CGFloat? { + switch position { + case .full: return 18.0 + case .half: return 262.0 + case .tip: return 69.0 + case .hidden: return nil + } + } +} + +public class FloatingPanelDefaultLandscapeLayout: FloatingPanelLayout { + public init() { } + + public var initialPosition: FloatingPanelPosition { + return .tip + } + public var supportedPositions: Set { + return [.full, .tip] + } + + public func insetFor(position: FloatingPanelPosition) -> CGFloat? { + switch position { + case .full: return 16.0 + case .tip: return 69.0 + default: return nil + } + } +} + +struct LayoutSegment { + let lower: FloatingPanelPosition? + let upper: FloatingPanelPosition? +} + +class FloatingPanelLayoutAdapter { + weak var vc: FloatingPanelController! + private weak var surfaceView: FloatingPanelSurfaceView! + private weak var backdropView: FloatingPanelBackdropView! + + var layout: FloatingPanelLayout { + didSet { + checkLayoutConsistance() + } + } + + private var safeAreaInsets: UIEdgeInsets { + return vc?.layoutInsets ?? .zero + } + + private var initialConst: CGFloat = 0.0 + + private var fixedConstraints: [NSLayoutConstraint] = [] + private var fullConstraints: [NSLayoutConstraint] = [] + private var halfConstraints: [NSLayoutConstraint] = [] + private var tipConstraints: [NSLayoutConstraint] = [] + private var offConstraints: [NSLayoutConstraint] = [] + private var interactiveTopConstraint: NSLayoutConstraint? + private var bottomConstraint: NSLayoutConstraint? + + + private var heightConstraint: NSLayoutConstraint? + + private var fullInset: CGFloat { + if layout is FloatingPanelIntrinsicLayout { + return intrinsicHeight + } else { + return layout.insetFor(position: .full) ?? 0.0 + } + } + private var halfInset: CGFloat { + return layout.insetFor(position: .half) ?? 0.0 + } + private var tipInset: CGFloat { + return layout.insetFor(position: .tip) ?? 0.0 + } + private var hiddenInset: CGFloat { + return layout.insetFor(position: .hidden) ?? 0.0 + } + + var supportedPositions: Set { + return layout.supportedPositions + } + + var topMostState: FloatingPanelPosition { + return supportedPositions.sorted(by: { $0.rawValue < $1.rawValue }).first ?? .hidden + } + + var bottomMostState: FloatingPanelPosition { + return supportedPositions.sorted(by: { $0.rawValue < $1.rawValue }).last ?? .hidden + } + + var topY: CGFloat { + return positionY(for: topMostState) + } + + var bottomY: CGFloat { + return positionY(for: bottomMostState) + } + + var topMaxY: CGFloat { + return topY - layout.topInteractionBuffer + } + + var bottomMaxY: CGFloat { + return bottomY + layout.bottomInteractionBuffer + } + + var adjustedContentInsets: UIEdgeInsets { + return UIEdgeInsets(top: 0.0, + left: 0.0, + bottom: safeAreaInsets.bottom, + right: 0.0) + } + + func positionY(for pos: FloatingPanelPosition) -> CGFloat { + switch pos { + case .full: + if layout is FloatingPanelIntrinsicLayout { + return surfaceView.superview!.bounds.height - surfaceView.bounds.height + } + switch layout.positionReference { + case .fromSafeArea: + return (safeAreaInsets.top + fullInset) + case .fromSuperview: + return fullInset + } + case .half: + switch layout.positionReference { + case .fromSafeArea: + return surfaceView.superview!.bounds.height - (safeAreaInsets.bottom + halfInset) + case .fromSuperview: + return surfaceView.superview!.bounds.height - halfInset + } + case .tip: + switch layout.positionReference { + case .fromSafeArea: + return surfaceView.superview!.bounds.height - (safeAreaInsets.bottom + tipInset) + case .fromSuperview: + return surfaceView.superview!.bounds.height - tipInset + } + case .hidden: + return surfaceView.superview!.bounds.height - hiddenInset + } + } + + var intrinsicHeight: CGFloat = 0.0 + + init(surfaceView: FloatingPanelSurfaceView, backdropView: FloatingPanelBackdropView, layout: FloatingPanelLayout) { + self.layout = layout + self.surfaceView = surfaceView + self.backdropView = backdropView + } + + func updateIntrinsicHeight() { + #if swift(>=4.2) + let fittingSize = UIView.layoutFittingCompressedSize + #else + let fittingSize = UILayoutFittingCompressedSize + #endif + var intrinsicHeight = surfaceView.contentView?.systemLayoutSizeFitting(fittingSize).height ?? 0.0 + var safeAreaBottom: CGFloat = 0.0 + if #available(iOS 11.0, *) { + safeAreaBottom = surfaceView.contentView?.safeAreaInsets.bottom ?? 0.0 + if safeAreaBottom > 0 { + intrinsicHeight -= safeAreaInsets.bottom + } + } + self.intrinsicHeight = max(intrinsicHeight, 0.0) + + log.debug("Update intrinsic height =", intrinsicHeight, + ", surface(height) =", surfaceView.frame.height, + ", content(height) =", surfaceView.contentView?.frame.height ?? 0.0, + ", content safe area(bottom) =", safeAreaBottom) + } + + func prepareLayout(in vc: FloatingPanelController) { + self.vc = vc + + NSLayoutConstraint.deactivate(fixedConstraints + fullConstraints + halfConstraints + tipConstraints + offConstraints) + NSLayoutConstraint.deactivate(constraint: self.heightConstraint) + self.heightConstraint = nil + NSLayoutConstraint.deactivate(constraint: self.bottomConstraint) + self.bottomConstraint = nil + + surfaceView.translatesAutoresizingMaskIntoConstraints = false + backdropView.translatesAutoresizingMaskIntoConstraints = false + + // Fixed constraints of surface and backdrop views + let surfaceConstraints = layout.prepareLayout(surfaceView: surfaceView, in: vc.view!) + let backdropConstraints = [ + backdropView.topAnchor.constraint(equalTo: vc.view.topAnchor, constant: 0.0), + backdropView.leftAnchor.constraint(equalTo: vc.view.leftAnchor,constant: 0.0), + backdropView.rightAnchor.constraint(equalTo: vc.view.rightAnchor, constant: 0.0), + backdropView.bottomAnchor.constraint(equalTo: vc.view.bottomAnchor, constant: 0.0), + ] + + fixedConstraints = surfaceConstraints + backdropConstraints + + if vc.contentMode == .fitToBounds { + bottomConstraint = surfaceView.bottomAnchor.constraint(equalTo: vc.view.bottomAnchor, + constant: 0.0) + } + + // Flexible surface constraints for full, half, tip and off + let topAnchor: NSLayoutYAxisAnchor = { + if layout.positionReference == .fromSuperview { + return vc.view.topAnchor + } else { + return vc.layoutGuide.topAnchor + } + }() + + switch layout { + case is FloatingPanelIntrinsicLayout: + // Set up on updateHeight() + break + default: + fullConstraints = [ + surfaceView.topAnchor.constraint(equalTo: topAnchor, + constant: fullInset), + ] + } + + let bottomAnchor: NSLayoutYAxisAnchor = { + if layout.positionReference == .fromSuperview { + return vc.view.bottomAnchor + } else { + return vc.layoutGuide.bottomAnchor + } + }() + + halfConstraints = [ + surfaceView.topAnchor.constraint(equalTo: bottomAnchor, + constant: -halfInset), + ] + tipConstraints = [ + surfaceView.topAnchor.constraint(equalTo: bottomAnchor, + constant: -tipInset), + ] + + offConstraints = [ + surfaceView.topAnchor.constraint(equalTo:vc.view.bottomAnchor, + constant: -hiddenInset), + ] + } + + func startInteraction(at state: FloatingPanelPosition, offset: CGPoint = .zero) { + guard self.interactiveTopConstraint == nil else { return } + NSLayoutConstraint.deactivate(fullConstraints + halfConstraints + tipConstraints + offConstraints) + + let interactiveTopConstraint: NSLayoutConstraint + switch layout.positionReference { + case .fromSafeArea: + initialConst = surfaceView.frame.minY - safeAreaInsets.top + offset.y + interactiveTopConstraint = surfaceView.topAnchor.constraint(equalTo: vc.layoutGuide.topAnchor, + constant: initialConst) + case .fromSuperview: + initialConst = surfaceView.frame.minY + offset.y + interactiveTopConstraint = surfaceView.topAnchor.constraint(equalTo: vc.view.topAnchor, + constant: initialConst) + } + NSLayoutConstraint.activate([interactiveTopConstraint]) + self.interactiveTopConstraint = interactiveTopConstraint + } + + func endInteraction(at state: FloatingPanelPosition) { + // Don't deactivate `interactiveTopConstraint` here because it leads to + // unsatisfiable constraints + + if self.interactiveTopConstraint == nil { + // Actiavate `interactiveTopConstraint` for `fitToBounds` mode. + // It goes throught this path when the pan gesture state jumps + // from .begin to .end. + startInteraction(at: state) + } + } + + // The method is separated from prepareLayout(to:) for the rotation support + // It must be called in FloatingPanelController.traitCollectionDidChange(_:) + func updateHeight() { + guard let vc = vc else { return } + NSLayoutConstraint.deactivate(constraint: heightConstraint) + heightConstraint = nil + + if layout is FloatingPanelIntrinsicLayout { + updateIntrinsicHeight() + } + defer { + if layout is FloatingPanelIntrinsicLayout { + NSLayoutConstraint.deactivate(fullConstraints) + fullConstraints = [ + surfaceView.topAnchor.constraint(equalTo: vc.layoutGuide.bottomAnchor, + constant: -fullInset), + ] + } + } + + guard vc.contentMode != .fitToBounds else { return } + + switch layout { + case is FloatingPanelIntrinsicLayout: + heightConstraint = surfaceView.heightAnchor.constraint(equalToConstant: intrinsicHeight + safeAreaInsets.bottom) + default: + let const = -(positionY(for: topMostState)) + heightConstraint = surfaceView.heightAnchor.constraint(equalTo: vc.view.heightAnchor, + constant: const) + } + NSLayoutConstraint.activate(constraint: heightConstraint) + + surfaceView.bottomOverflow = vc.view.bounds.height + layout.topInteractionBuffer + } + + func updateInteractiveTopConstraint(diff: CGFloat, allowsTopBuffer: Bool, with behavior: FloatingPanelBehavior) { + defer { + layoutSurfaceIfNeeded() // MUST be called to update `surfaceView.frame` + } + + let topMostConst: CGFloat = { + var ret: CGFloat = 0.0 + switch layout.positionReference { + case .fromSafeArea: + ret = topY - safeAreaInsets.top + case .fromSuperview: + ret = topY + } + return max(ret, 0.0) // The top boundary is equal to the related topAnchor. + }() + let bottomMostConst: CGFloat = { + var ret: CGFloat = 0.0 + let _bottomY = vc.isRemovalInteractionEnabled ? positionY(for: .hidden) : bottomY + switch layout.positionReference { + case .fromSafeArea: + ret = _bottomY - safeAreaInsets.top + case .fromSuperview: + ret = _bottomY + } + return min(ret, surfaceView.superview!.bounds.height) + }() + let minConst = allowsTopBuffer ? topMostConst - layout.topInteractionBuffer : topMostConst + let maxConst = bottomMostConst + layout.bottomInteractionBuffer + + var const = initialConst + diff + + // Rubberbanding top buffer + if behavior.allowsRubberBanding(for: .top), const < topMostConst { + let buffer = topMostConst - const + const = topMostConst - rubberbandEffect(for: buffer, base: vc.view.bounds.height) + } + + // Rubberbanding bottom buffer + if behavior.allowsRubberBanding(for: .bottom), const > bottomMostConst { + let buffer = const - bottomMostConst + const = bottomMostConst + rubberbandEffect(for: buffer, base: vc.view.bounds.height) + } + + interactiveTopConstraint?.constant = max(minConst, min(maxConst, const)) + } + + // According to @chpwn's tweet: https://twitter.com/chpwn/status/285540192096497664 + // x = distance from the edge + // c = constant value, UIScrollView uses 0.55 + // d = dimension, either width or height + private func rubberbandEffect(for buffer: CGFloat, base: CGFloat) -> CGFloat { + return (1.0 - (1.0 / ((buffer * 0.55 / base) + 1.0))) * base + } + + func activateFixedLayout() { + // Must deactivate `interactiveTopConstraint` here + NSLayoutConstraint.deactivate(constraint: self.interactiveTopConstraint) + self.interactiveTopConstraint = nil + + NSLayoutConstraint.activate(fixedConstraints) + + if vc.contentMode == .fitToBounds { + NSLayoutConstraint.activate(constraint: self.bottomConstraint) + } + } + + func activateInteractiveLayout(of state: FloatingPanelPosition) { + defer { + layoutSurfaceIfNeeded() + log.debug("activateLayout -- surface.presentation = \(self.surfaceView.presentationFrame) surface.frame = \(self.surfaceView.frame)") + } + + var state = state + + setBackdropAlpha(of: state) + + if isValid(state) == false { + state = layout.initialPosition + } + + NSLayoutConstraint.deactivate(fullConstraints + halfConstraints + tipConstraints + offConstraints) + switch state { + case .full: + NSLayoutConstraint.activate(fullConstraints) + case .half: + NSLayoutConstraint.activate(halfConstraints) + case .tip: + NSLayoutConstraint.activate(tipConstraints) + case .hidden: + NSLayoutConstraint.activate(offConstraints) + } + } + + func activateLayout(of state: FloatingPanelPosition) { + activateFixedLayout() + activateInteractiveLayout(of: state) + } + + func isValid(_ state: FloatingPanelPosition) -> Bool { + return supportedPositions.union([.hidden]).contains(state) + } + + private func layoutSurfaceIfNeeded() { + #if !TEST + guard surfaceView.window != nil else { return } + #endif + surfaceView.superview?.layoutIfNeeded() + } + + private func setBackdropAlpha(of target: FloatingPanelPosition) { + if target == .hidden { + self.backdropView.alpha = 0.0 + } else { + self.backdropView.alpha = layout.backdropAlphaFor(position: target) + } + } + + private func checkLayoutConsistance() { + // Verify layout configurations + assert(supportedPositions.count > 0) + assert(supportedPositions.contains(layout.initialPosition), + "Does not include an initial position (\(layout.initialPosition)) in supportedPositions (\(supportedPositions))") + + if layout is FloatingPanelIntrinsicLayout { + assert(layout.insetFor(position: .full) == nil, "Return `nil` for full position on FloatingPanelIntrinsicLayout") + } + + if halfInset > 0 { + assert(halfInset > tipInset, "Invalid half and tip insets") + } + // The verification isn't working on orientation change(portrait -> landscape) + // of a floating panel in tab bar. Because the `safeAreaInsets.bottom` is + // updated in delay so that it can be 83.0(not 53.0) even after the surface + // and the super view's frame is fit to landscape already. + /*if fullInset > 0 { + assert(middleY > topY, "Invalid insets { topY: \(topY), middleY: \(middleY) }") + assert(bottomY > topY, "Invalid insets { topY: \(topY), bottomY: \(bottomY) }") + }*/ + } + + func segument(at posY: CGFloat, forward: Bool) -> LayoutSegment { + /// ----------------------->Y + /// --> forward <-- backward + /// |-------|===o===|-------| |-------|-------|===o===| + /// |-------|-------x=======| |-------|=======x-------| + /// |-------|-------|===o===| |-------|===o===|-------| + /// pos: o/x, seguement: = + let sortedPositions = supportedPositions.sorted(by: { $0.rawValue < $1.rawValue }) + + let upperIndex: Int? + if forward { + #if swift(>=4.2) + upperIndex = sortedPositions.firstIndex(where: { posY < positionY(for: $0) }) + #else + upperIndex = sortedPositions.index(where: { posY < positionY(for: $0) }) + #endif + } else { + #if swift(>=4.2) + upperIndex = sortedPositions.firstIndex(where: { posY <= positionY(for: $0) }) + #else + upperIndex = sortedPositions.index(where: { posY <= positionY(for: $0) }) + #endif + } + + switch upperIndex { + case 0: + return LayoutSegment(lower: nil, upper: sortedPositions.first) + case let upperIndex?: + return LayoutSegment(lower: sortedPositions[upperIndex - 1], upper: sortedPositions[upperIndex]) + default: + return LayoutSegment(lower: sortedPositions[sortedPositions.endIndex - 1], upper: nil) + } + } +} diff --git a/Framework/Tests/FloatingPanelControllerTests.swift b/Framework/Tests/FloatingPanelControllerTests.swift new file mode 100644 index 00000000..f1d3d421 --- /dev/null +++ b/Framework/Tests/FloatingPanelControllerTests.swift @@ -0,0 +1,188 @@ +// +// Created by Shin Yamamoto on 2018/09/18. +// Copyright © 2018 Shin Yamamoto. All rights reserved. +// + +import XCTest +@testable import FloatingPanel + +class FloatingPanelControllerTests: XCTestCase { + override func setUp() {} + override func tearDown() {} + + func test_warningRetainCycle() { + let myVC = MyZombieViewController(nibName: nil, bundle: nil) + let exp = expectation(description: "Warning retain cycle") + exp.expectedFulfillmentCount = 2 // For layout & behavior logs + log.hook = {(log, level) in + if log.contains("A memory leak will occur by a retain cycle because") { + XCTAssert(level == .warning) + exp.fulfill() + } + } + myVC.loadViewIfNeeded() + wait(for: [exp], timeout: 10) + } + + func test_addPanel() { + guard let rootVC = UIApplication.shared.keyWindow?.rootViewController else { fatalError() } + let fpc = FloatingPanelController() + fpc.addPanel(toParent: rootVC) + XCTAssert(fpc.surfaceView.frame.minY == (fpc.view.bounds.height - fpc.layoutInsets.bottom) - fpc.layout.insetFor(position: .half)!) + fpc.move(to: .tip, animated: false) + XCTAssert(fpc.surfaceView.frame.minY == (fpc.view.bounds.height - fpc.layoutInsets.bottom) - fpc.layout.insetFor(position: .tip)!) + } + + @available(iOS 12.0, *) + func test_updateLayout_willTransition() { + class MyDelegate: FloatingPanelControllerDelegate { + func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? { + if newCollection.userInterfaceStyle == .dark { + XCTFail() + } + return nil + } + } + let myDelegate = MyDelegate() + let fpc = FloatingPanelController(delegate: myDelegate) + let traitCollection = UITraitCollection(traitsFrom: [fpc.traitCollection, + UITraitCollection(userInterfaceStyle: .dark)]) + XCTAssertEqual(traitCollection.userInterfaceStyle, .dark) + fpc.prepare(for: traitCollection) + } + + func test_moveTo() { + let delegate = FloatingPanelTestDelegate() + let fpc = FloatingPanelController(delegate: delegate) + XCTAssertEqual(delegate.position, .hidden) + fpc.showForTest() + XCTAssertEqual(delegate.position, .half) + + fpc.hide() + XCTAssertEqual(delegate.position, .hidden) + + fpc.move(to: .full, animated: false) + XCTAssertEqual(fpc.position, .full) + XCTAssertEqual(delegate.position, .full) + XCTAssertEqual(fpc.surfaceView.frame.minY, fpc.originYOfSurface(for: .full)) + + fpc.move(to: .half, animated: false) + XCTAssertEqual(fpc.position, .half) + XCTAssertEqual(delegate.position, .half) + XCTAssertEqual(fpc.surfaceView.frame.minY, fpc.originYOfSurface(for: .half)) + + fpc.move(to: .tip, animated: false) + XCTAssertEqual(fpc.position, .tip) + XCTAssertEqual(delegate.position, .tip) + XCTAssertEqual(fpc.surfaceView.frame.minY, fpc.originYOfSurface(for: .tip)) + + fpc.move(to: .hidden, animated: false) + XCTAssertEqual(fpc.position, .hidden) + XCTAssertEqual(delegate.position, .hidden) + XCTAssertEqual(fpc.surfaceView.frame.minY, fpc.originYOfSurface(for: .hidden)) + + fpc.move(to: .full, animated: true) + XCTAssertEqual(fpc.position, .full) + XCTAssertEqual(delegate.position, .full) + XCTAssertEqual(fpc.surfaceView.frame.minY, fpc.originYOfSurface(for: .full)) + + fpc.move(to: .half, animated: true) + XCTAssertEqual(fpc.position, .half) + XCTAssertEqual(delegate.position, .half) + XCTAssertEqual(fpc.surfaceView.frame.minY, fpc.originYOfSurface(for: .half)) + + fpc.move(to: .tip, animated: true) + XCTAssertEqual(fpc.position, .tip) + XCTAssertEqual(delegate.position, .tip) + XCTAssertEqual(fpc.surfaceView.frame.minY, fpc.originYOfSurface(for: .tip)) + + fpc.move(to: .hidden, animated: true) + XCTAssertEqual(fpc.position, .hidden) + XCTAssertEqual(delegate.position, .hidden) + XCTAssertEqual(fpc.surfaceView.frame.minY, fpc.originYOfSurface(for: .hidden)) + } + + func test_moveWithNearbyPosition() { + let delegate = FloatingPanelTestDelegate() + let fpc = FloatingPanelController(delegate: delegate) + XCTAssertEqual(delegate.position, .hidden) + fpc.showForTest() + + XCTAssertEqual(fpc.nearbyPosition, .half) + + fpc.hide() + XCTAssertEqual(fpc.nearbyPosition, .tip) + + fpc.move(to: .full, animated: false) + XCTAssertEqual(fpc.nearbyPosition, .full) + XCTAssertEqual(fpc.surfaceView.frame.minY, fpc.originYOfSurface(for: .full)) + } + + func test_originSurfaceY() { + let fpc = FloatingPanelController(delegate: nil) + fpc.loadViewIfNeeded() + fpc.view.frame = CGRect(x: 0, y: 0, width: 375, height: 667) + fpc.show(animated: false, completion: nil) + + fpc.move(to: .full, animated: false) + XCTAssertEqual(fpc.surfaceView.frame.minY, fpc.originYOfSurface(for: .full)) + fpc.move(to: .half, animated: false) + XCTAssertEqual(fpc.surfaceView.frame.minY, fpc.originYOfSurface(for: .half)) + fpc.move(to: .tip, animated: false) + XCTAssertEqual(fpc.surfaceView.frame.minY, fpc.originYOfSurface(for: .tip)) + fpc.move(to: .hidden, animated: false) + XCTAssertEqual(fpc.surfaceView.frame.minY, fpc.originYOfSurface(for: .hidden)) + } + + func test_contentMode() { + let fpc = FloatingPanelController(delegate: nil) + fpc.loadViewIfNeeded() + fpc.view.frame = CGRect(x: 0, y: 0, width: 375, height: 667) + fpc.show(animated: false, completion: nil) + + fpc.contentMode = .static + + fpc.move(to: .full, animated: false) + XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.originYOfSurface(for: .full)) + fpc.move(to: .half, animated: false) + XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.originYOfSurface(for: .full)) + fpc.move(to: .tip, animated: false) + XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.originYOfSurface(for: .full)) + + fpc.contentMode = .fitToBounds + + fpc.move(to: .full, animated: false) + XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.originYOfSurface(for: .full)) + fpc.move(to: .half, animated: false) + XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.originYOfSurface(for: .half)) + fpc.move(to: .tip, animated: false) + XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.originYOfSurface(for: .tip)) + } +} + +private class MyZombieViewController: UIViewController, FloatingPanelLayout, FloatingPanelBehavior, FloatingPanelControllerDelegate { + var fpc: FloatingPanelController? + override func viewDidLoad() { + fpc = FloatingPanelController(delegate: self) + fpc?.addPanel(toParent: self) + } + func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? { + return self + } + + func floatingPanel(_ vc: FloatingPanelController, behaviorFor newCollection: UITraitCollection) -> FloatingPanelBehavior? { + return self + } + var initialPosition: FloatingPanelPosition { + return .half + } + + func insetFor(position: FloatingPanelPosition) -> CGFloat? { + switch position { + case .full: return UIScreen.main.bounds.height == 667.0 ? 18.0 : 16.0 + case .half: return 262.0 + case .tip: return 69.0 + case .hidden: return nil + } + } +} diff --git a/Sources/Controller.swift b/Sources/Controller.swift index 2245a576..0707d807 100644 --- a/Sources/Controller.swift +++ b/Sources/Controller.swift @@ -125,6 +125,9 @@ import os.log shouldAllowToScroll scrollView: UIScrollView, in state: FloatingPanelState ) -> Bool + + @objc(floatingPanelDidViewLayout:) optional + func floatingPanelDidViewLayout(_ vc: FloatingPanelController) } /// @@ -334,6 +337,7 @@ open class FloatingPanelController: UIViewController { if contentMode == .static { floatingPanel.layoutAdapter.updateStaticConstraint() } + self.delegate?.floatingPanelDidViewLayout?(self) } open override func viewDidAppear(_ animated: Bool) {