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) {