diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bad48f2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +**/xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +**/build/ +**/DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Gcc Patch +**/*.gcno + +## MacOS +.DS_Store diff --git a/CustomInterfaceBuilderView/CustomInterfaceBuilderView.xcodeproj/project.pbxproj b/CustomInterfaceBuilderView/CustomInterfaceBuilderView.xcodeproj/project.pbxproj new file mode 100644 index 0000000..d12932e --- /dev/null +++ b/CustomInterfaceBuilderView/CustomInterfaceBuilderView.xcodeproj/project.pbxproj @@ -0,0 +1,360 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + ED29E78826EA5E25001242EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED29E78726EA5E25001242EE /* AppDelegate.swift */; }; + ED29E78A26EA5E25001242EE /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED29E78926EA5E25001242EE /* SceneDelegate.swift */; }; + ED29E78C26EA5E25001242EE /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED29E78B26EA5E25001242EE /* ViewController.swift */; }; + ED29E78F26EA5E25001242EE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = ED29E78D26EA5E25001242EE /* Main.storyboard */; }; + ED29E79126EA5E27001242EE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = ED29E79026EA5E27001242EE /* Assets.xcassets */; }; + ED29E79426EA5E27001242EE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = ED29E79226EA5E27001242EE /* LaunchScreen.storyboard */; }; + ED29E7B826EA613D001242EE /* RatingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED29E7B726EA613D001242EE /* RatingView.swift */; }; + ED29E7BA26EA6152001242EE /* RoundedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED29E7B926EA6152001242EE /* RoundedView.swift */; }; + ED29E7BD26EBCA79001242EE /* CircularView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED29E7BC26EBCA79001242EE /* CircularView.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + ED29E78426EA5E25001242EE /* CustomInterfaceBuilderView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CustomInterfaceBuilderView.app; sourceTree = BUILT_PRODUCTS_DIR; }; + ED29E78726EA5E25001242EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + ED29E78926EA5E25001242EE /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + ED29E78B26EA5E25001242EE /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + ED29E78E26EA5E25001242EE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + ED29E79026EA5E27001242EE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + ED29E79326EA5E27001242EE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + ED29E79526EA5E27001242EE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + ED29E7B726EA613D001242EE /* RatingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RatingView.swift; sourceTree = ""; }; + ED29E7B926EA6152001242EE /* RoundedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedView.swift; sourceTree = ""; }; + ED29E7BB26EBBC24001242EE /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + ED29E7BC26EBCA79001242EE /* CircularView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularView.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + ED29E78126EA5E25001242EE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + ED29E77B26EA5E25001242EE = { + isa = PBXGroup; + children = ( + ED29E7BB26EBBC24001242EE /* README.md */, + ED29E78626EA5E25001242EE /* CustomInterfaceBuilderView */, + ED29E78526EA5E25001242EE /* Products */, + ); + sourceTree = ""; + }; + ED29E78526EA5E25001242EE /* Products */ = { + isa = PBXGroup; + children = ( + ED29E78426EA5E25001242EE /* CustomInterfaceBuilderView.app */, + ); + name = Products; + sourceTree = ""; + }; + ED29E78626EA5E25001242EE /* CustomInterfaceBuilderView */ = { + isa = PBXGroup; + children = ( + ED29E78726EA5E25001242EE /* AppDelegate.swift */, + ED29E78926EA5E25001242EE /* SceneDelegate.swift */, + ED29E78D26EA5E25001242EE /* Main.storyboard */, + ED29E78B26EA5E25001242EE /* ViewController.swift */, + ED29E7B726EA613D001242EE /* RatingView.swift */, + ED29E7B926EA6152001242EE /* RoundedView.swift */, + ED29E7BC26EBCA79001242EE /* CircularView.swift */, + ED29E79026EA5E27001242EE /* Assets.xcassets */, + ED29E79226EA5E27001242EE /* LaunchScreen.storyboard */, + ED29E79526EA5E27001242EE /* Info.plist */, + ); + path = CustomInterfaceBuilderView; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + ED29E78326EA5E25001242EE /* CustomInterfaceBuilderView */ = { + isa = PBXNativeTarget; + buildConfigurationList = ED29E7AE26EA5E28001242EE /* Build configuration list for PBXNativeTarget "CustomInterfaceBuilderView" */; + buildPhases = ( + ED29E78026EA5E25001242EE /* Sources */, + ED29E78126EA5E25001242EE /* Frameworks */, + ED29E78226EA5E25001242EE /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = CustomInterfaceBuilderView; + productName = CustomInterfaceBuilderView; + productReference = ED29E78426EA5E25001242EE /* CustomInterfaceBuilderView.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + ED29E77C26EA5E25001242EE /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1250; + LastUpgradeCheck = 1250; + TargetAttributes = { + ED29E78326EA5E25001242EE = { + CreatedOnToolsVersion = 12.5.1; + }; + }; + }; + buildConfigurationList = ED29E77F26EA5E25001242EE /* Build configuration list for PBXProject "CustomInterfaceBuilderView" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = ED29E77B26EA5E25001242EE; + productRefGroup = ED29E78526EA5E25001242EE /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + ED29E78326EA5E25001242EE /* CustomInterfaceBuilderView */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + ED29E78226EA5E25001242EE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ED29E79426EA5E27001242EE /* LaunchScreen.storyboard in Resources */, + ED29E79126EA5E27001242EE /* Assets.xcassets in Resources */, + ED29E78F26EA5E25001242EE /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + ED29E78026EA5E25001242EE /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ED29E7BD26EBCA79001242EE /* CircularView.swift in Sources */, + ED29E78C26EA5E25001242EE /* ViewController.swift in Sources */, + ED29E78826EA5E25001242EE /* AppDelegate.swift in Sources */, + ED29E78A26EA5E25001242EE /* SceneDelegate.swift in Sources */, + ED29E7BA26EA6152001242EE /* RoundedView.swift in Sources */, + ED29E7B826EA613D001242EE /* RatingView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + ED29E78D26EA5E25001242EE /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + ED29E78E26EA5E25001242EE /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + ED29E79226EA5E27001242EE /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + ED29E79326EA5E27001242EE /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + ED29E7AC26EA5E28001242EE /* 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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + 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 = 14.5; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + ED29E7AD26EA5E28001242EE /* 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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + 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 = 14.5; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + ED29E7AF26EA5E28001242EE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = CustomInterfaceBuilderView/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = ios.example.CustomInterfaceBuilderView; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + ED29E7B026EA5E28001242EE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = CustomInterfaceBuilderView/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = ios.example.CustomInterfaceBuilderView; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + ED29E77F26EA5E25001242EE /* Build configuration list for PBXProject "CustomInterfaceBuilderView" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + ED29E7AC26EA5E28001242EE /* Debug */, + ED29E7AD26EA5E28001242EE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + ED29E7AE26EA5E28001242EE /* Build configuration list for PBXNativeTarget "CustomInterfaceBuilderView" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + ED29E7AF26EA5E28001242EE /* Debug */, + ED29E7B026EA5E28001242EE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = ED29E77C26EA5E25001242EE /* Project object */; +} diff --git a/CustomInterfaceBuilderView/CustomInterfaceBuilderView.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/CustomInterfaceBuilderView/CustomInterfaceBuilderView.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/CustomInterfaceBuilderView/CustomInterfaceBuilderView.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/CustomInterfaceBuilderView/CustomInterfaceBuilderView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/CustomInterfaceBuilderView/CustomInterfaceBuilderView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/CustomInterfaceBuilderView/CustomInterfaceBuilderView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/CustomInterfaceBuilderView/CustomInterfaceBuilderView/AppDelegate.swift b/CustomInterfaceBuilderView/CustomInterfaceBuilderView/AppDelegate.swift new file mode 100644 index 0000000..ec83885 --- /dev/null +++ b/CustomInterfaceBuilderView/CustomInterfaceBuilderView/AppDelegate.swift @@ -0,0 +1,36 @@ +// +// AppDelegate.swift +// CustomInterfaceBuilderView +// +// Created by Thomas Asheim Smedmann on 09/09/2021. +// + +import UIKit + +@main +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/CustomInterfaceBuilderView/CustomInterfaceBuilderView/Assets.xcassets/AccentColor.colorset/Contents.json b/CustomInterfaceBuilderView/CustomInterfaceBuilderView/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/CustomInterfaceBuilderView/CustomInterfaceBuilderView/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/CustomInterfaceBuilderView/CustomInterfaceBuilderView/Assets.xcassets/AppIcon.appiconset/Contents.json b/CustomInterfaceBuilderView/CustomInterfaceBuilderView/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..9221b9b --- /dev/null +++ b/CustomInterfaceBuilderView/CustomInterfaceBuilderView/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/CustomInterfaceBuilderView/CustomInterfaceBuilderView/Assets.xcassets/Contents.json b/CustomInterfaceBuilderView/CustomInterfaceBuilderView/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/CustomInterfaceBuilderView/CustomInterfaceBuilderView/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/CustomInterfaceBuilderView/CustomInterfaceBuilderView/Base.lproj/LaunchScreen.storyboard b/CustomInterfaceBuilderView/CustomInterfaceBuilderView/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..865e932 --- /dev/null +++ b/CustomInterfaceBuilderView/CustomInterfaceBuilderView/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CustomInterfaceBuilderView/CustomInterfaceBuilderView/Base.lproj/Main.storyboard b/CustomInterfaceBuilderView/CustomInterfaceBuilderView/Base.lproj/Main.storyboard new file mode 100644 index 0000000..29efe8a --- /dev/null +++ b/CustomInterfaceBuilderView/CustomInterfaceBuilderView/Base.lproj/Main.storyboard @@ -0,0 +1,205 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CustomInterfaceBuilderView/CustomInterfaceBuilderView/CircularView.swift b/CustomInterfaceBuilderView/CustomInterfaceBuilderView/CircularView.swift new file mode 100644 index 0000000..38297a7 --- /dev/null +++ b/CustomInterfaceBuilderView/CustomInterfaceBuilderView/CircularView.swift @@ -0,0 +1,17 @@ +// +// CircleView.swift +// CustomInterfaceBuilderView +// +// Created by Thomas Asheim Smedmann on 10/09/2021. +// + +import UIKit + +@IBDesignable +class CircularView: UIView { + override func layoutSubviews() { + super.layoutSubviews() + layer.cornerRadius = CGFloat(min(bounds.width / 2, bounds.height / 2)) + layer.masksToBounds = true + } +} diff --git a/CustomInterfaceBuilderView/CustomInterfaceBuilderView/Info.plist b/CustomInterfaceBuilderView/CustomInterfaceBuilderView/Info.plist new file mode 100644 index 0000000..5b531f7 --- /dev/null +++ b/CustomInterfaceBuilderView/CustomInterfaceBuilderView/Info.plist @@ -0,0 +1,66 @@ + + + + + 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 + + + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/CustomInterfaceBuilderView/CustomInterfaceBuilderView/RatingView.swift b/CustomInterfaceBuilderView/CustomInterfaceBuilderView/RatingView.swift new file mode 100644 index 0000000..60ab909 --- /dev/null +++ b/CustomInterfaceBuilderView/CustomInterfaceBuilderView/RatingView.swift @@ -0,0 +1,122 @@ +// +// RatingView.swift +// CustomInterfaceBuilderView +// +// Created by Thomas Asheim Smedmann on 09/09/2021. +// + +import UIKit + + +enum RatingThreshold: Int { + case low = 4 // 1..<4 = low + case high = 8 // 8..<11 = high +} + +@IBDesignable +class RatingView: UIView { + private static let dotWidth: CGFloat = 10 + private static let ratingLabelMargin: CGFloat = 1 + + private static let maxRating = 10 + + private let container: UIStackView = { + let stackView = UIStackView(arrangedSubviews: (0.. 0 + ? RatingView.dotWidth * 2 * CGFloat(RatingView.maxRating) + ratingLabel.intrinsicContentSize.width + 2 * RatingView.ratingLabelMargin + : RatingView.dotWidth * 2 * CGFloat(RatingView.maxRating) + RatingView.dotWidth + + let height = rating > 0 + ? ratingLabel.intrinsicContentSize.height + 2 * RatingView.ratingLabelMargin + : RatingView.dotWidth + + return CGSize(width: width, height: height) + } + + override class var requiresConstraintBasedLayout: Bool { + return true + } + + private func configureUI() { + translatesAutoresizingMaskIntoConstraints = false + + addSubview(container) + + container.topAnchor.constraint(greaterThanOrEqualTo: topAnchor).isActive = true + container.bottomAnchor.constraint(greaterThanOrEqualTo: bottomAnchor).isActive = true + container.leftAnchor.constraint(greaterThanOrEqualTo: leftAnchor).isActive = true + container.rightAnchor.constraint(greaterThanOrEqualTo: rightAnchor).isActive = true + container.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true + container.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true + } + + private func updateUI() { + if let dotView = ratingLabel.superview { + dotView.backgroundColor = .white + dotView.constraints.first(where: { $0.firstAttribute == .width })?.constant = RatingView.dotWidth + ratingLabel.removeFromSuperview() + } + + let index = rating - 1 + guard + index > -1, index < container.arrangedSubviews.count + else { return } + + let dotView = container.arrangedSubviews[index] + + ratingLabel.text = "\(rating)" + + dotView.addSubview(ratingLabel) + dotView.backgroundColor = rating < RatingThreshold.high.rawValue + ? rating < RatingThreshold.low.rawValue ? .red : .white + : .green + + ratingLabel.centerYAnchor.constraint(equalTo: dotView.centerYAnchor).isActive = true + ratingLabel.centerXAnchor.constraint(equalTo: dotView.centerXAnchor).isActive = true + + dotView.constraints.first(where: { $0.firstAttribute == .width })?.constant = ratingLabel.intrinsicContentSize.height + RatingView.ratingLabelMargin * 2 + } +} + diff --git a/CustomInterfaceBuilderView/CustomInterfaceBuilderView/RoundedView.swift b/CustomInterfaceBuilderView/CustomInterfaceBuilderView/RoundedView.swift new file mode 100644 index 0000000..c46b51f --- /dev/null +++ b/CustomInterfaceBuilderView/CustomInterfaceBuilderView/RoundedView.swift @@ -0,0 +1,92 @@ +// +// RoundedView.swift +// CustomInterfaceBuilderView +// +// Created by Thomas Asheim Smedmann on 09/09/2021. +// + +import UIKit + +@IBDesignable +class RoundedView: UIView { + private let borderView: UIView = { + let view = UIView() + view.translatesAutoresizingMaskIntoConstraints = false + return view + }() + + @IBInspectable var cornerRadius: Int = 0 { + didSet { + layer.cornerRadius = CGFloat(cornerRadius) + borderView.layer.cornerRadius = CGFloat(cornerRadius) + borderView.layer.masksToBounds = cornerRadius > 0 + } + } + + @IBInspectable var borderWidth: Int = 0 { + didSet { + borderView.layer.borderWidth = CGFloat(borderWidth) + } + } + + @IBInspectable var borderColor: UIColor = .clear { + didSet { + borderView.layer.borderColor = borderColor.cgColor + } + } + + @IBInspectable var shadowColor: UIColor = .black { + didSet { + layer.shadowColor = shadowColor.cgColor + } + } + + @IBInspectable var shadowRadius: Int = 0 { + didSet { + layer.shadowRadius = CGFloat(shadowRadius) + } + } + + @IBInspectable var shadowOpacity: Float = 0 { + didSet { + layer.shadowOpacity = shadowOpacity + } + } + + @IBInspectable var shadowOffset: CGSize = CGSize(width: 0, height: 0) { + didSet { + layer.shadowOffset = shadowOffset + } + } + + override func layoutSubviews() { + super.layoutSubviews() + + layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: layer.cornerRadius).cgPath + layer.shouldRasterize = true + layer.rasterizationScale = UIScreen.main.scale + } + + override init(frame: CGRect) { + super.init(frame: frame) + configure() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + configure() + } + + private func configure() { + translatesAutoresizingMaskIntoConstraints = false + + addSubview(borderView) + + NSLayoutConstraint.activate([ + borderView.topAnchor.constraint(equalTo: topAnchor), + borderView.bottomAnchor.constraint(equalTo: bottomAnchor), + borderView.leftAnchor.constraint(equalTo: leftAnchor), + borderView.rightAnchor.constraint(equalTo: rightAnchor), + ]) + } +} diff --git a/CustomInterfaceBuilderView/CustomInterfaceBuilderView/SceneDelegate.swift b/CustomInterfaceBuilderView/CustomInterfaceBuilderView/SceneDelegate.swift new file mode 100644 index 0000000..0f2d12f --- /dev/null +++ b/CustomInterfaceBuilderView/CustomInterfaceBuilderView/SceneDelegate.swift @@ -0,0 +1,52 @@ +// +// SceneDelegate.swift +// CustomInterfaceBuilderView +// +// Created by Thomas Asheim Smedmann on 09/09/2021. +// + +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 necessarily 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/CustomInterfaceBuilderView/CustomInterfaceBuilderView/ViewController.swift b/CustomInterfaceBuilderView/CustomInterfaceBuilderView/ViewController.swift new file mode 100644 index 0000000..771c2f0 --- /dev/null +++ b/CustomInterfaceBuilderView/CustomInterfaceBuilderView/ViewController.swift @@ -0,0 +1,25 @@ +// +// ViewController.swift +// CustomInterfaceBuilderView +// +// Created by Thomas Asheim Smedmann on 09/09/2021. +// + +import UIKit + +class ViewController: UIViewController { + @IBOutlet weak var ratingView: RatingView! + @IBOutlet weak var circularViewWidthConstraint: NSLayoutConstraint! + + @IBAction func handleRatingChange(_ sender: UISlider) { + let newRating = Int(floor(sender.value)) + if newRating == ratingView.rating { return } + ratingView.rating = newRating + circularViewWidthConstraint.constant = CGFloat(newRating * 10) + } + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + } +} diff --git a/CustomInterfaceBuilderView/README.md b/CustomInterfaceBuilderView/README.md new file mode 100644 index 0000000..f6d93f6 --- /dev/null +++ b/CustomInterfaceBuilderView/README.md @@ -0,0 +1,3 @@ +# Custom Interface Builder View + +A simple example to showcase the use of [@IBDesignable and @IBInspectable](https://nshipster.com/ibinspectable-ibdesignable/) to build your own custom Interface Builder View. diff --git a/CustomPresentationController/CustomPresentationController.xcodeproj/project.pbxproj b/CustomPresentationController/CustomPresentationController.xcodeproj/project.pbxproj new file mode 100644 index 0000000..ab36071 --- /dev/null +++ b/CustomPresentationController/CustomPresentationController.xcodeproj/project.pbxproj @@ -0,0 +1,373 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + ED4F37B827355D1600B47391 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED4F37B727355D1600B47391 /* AppDelegate.swift */; }; + ED4F37BA27355D1600B47391 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED4F37B927355D1600B47391 /* SceneDelegate.swift */; }; + ED4F37BC27355D1600B47391 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED4F37BB27355D1600B47391 /* ViewController.swift */; }; + ED4F37C127355D1700B47391 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = ED4F37C027355D1700B47391 /* Assets.xcassets */; }; + ED4F37C427355D1700B47391 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = ED4F37C227355D1700B47391 /* LaunchScreen.storyboard */; }; + ED4F37CD2735608E00B47391 /* ShelfTransitionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED4F37CC2735608E00B47391 /* ShelfTransitionManager.swift */; }; + ED4F37CF273560F300B47391 /* ContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED4F37CE273560F300B47391 /* ContentViewController.swift */; }; + ED4F37D12735BC2600B47391 /* DialogTransitionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED4F37D02735BC2600B47391 /* DialogTransitionManager.swift */; }; + ED8F4199273AF44C0080C26F /* VisualEffectsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED8F4198273AF44C0080C26F /* VisualEffectsViewController.swift */; }; + ED8F419B273B06BC0080C26F /* MotionEffectsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED8F419A273B06BC0080C26F /* MotionEffectsViewController.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + ED4F37B427355D1600B47391 /* CustomPresentationController.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CustomPresentationController.app; sourceTree = BUILT_PRODUCTS_DIR; }; + ED4F37B727355D1600B47391 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + ED4F37B927355D1600B47391 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + ED4F37BB27355D1600B47391 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + ED4F37C027355D1700B47391 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + ED4F37C327355D1700B47391 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + ED4F37C527355D1700B47391 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + ED4F37CB27355D2C00B47391 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + ED4F37CC2735608E00B47391 /* ShelfTransitionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShelfTransitionManager.swift; sourceTree = ""; }; + ED4F37CE273560F300B47391 /* ContentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentViewController.swift; sourceTree = ""; }; + ED4F37D02735BC2600B47391 /* DialogTransitionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DialogTransitionManager.swift; sourceTree = ""; }; + ED8F4198273AF44C0080C26F /* VisualEffectsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisualEffectsViewController.swift; sourceTree = ""; }; + ED8F419A273B06BC0080C26F /* MotionEffectsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MotionEffectsViewController.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + ED4F37B127355D1600B47391 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + ED4F37AB27355D1600B47391 = { + isa = PBXGroup; + children = ( + ED4F37CB27355D2C00B47391 /* README.md */, + ED4F37B627355D1600B47391 /* CustomPresentationController */, + ED4F37B527355D1600B47391 /* Products */, + ); + sourceTree = ""; + }; + ED4F37B527355D1600B47391 /* Products */ = { + isa = PBXGroup; + children = ( + ED4F37B427355D1600B47391 /* CustomPresentationController.app */, + ); + name = Products; + sourceTree = ""; + }; + ED4F37B627355D1600B47391 /* CustomPresentationController */ = { + isa = PBXGroup; + children = ( + ED4F37B727355D1600B47391 /* AppDelegate.swift */, + ED4F37B927355D1600B47391 /* SceneDelegate.swift */, + ED4F37BB27355D1600B47391 /* ViewController.swift */, + ED8F4198273AF44C0080C26F /* VisualEffectsViewController.swift */, + ED8F419A273B06BC0080C26F /* MotionEffectsViewController.swift */, + ED4F37CE273560F300B47391 /* ContentViewController.swift */, + ED4F37CC2735608E00B47391 /* ShelfTransitionManager.swift */, + ED4F37D02735BC2600B47391 /* DialogTransitionManager.swift */, + ED4F37C027355D1700B47391 /* Assets.xcassets */, + ED4F37C227355D1700B47391 /* LaunchScreen.storyboard */, + ED4F37C527355D1700B47391 /* Info.plist */, + ); + path = CustomPresentationController; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + ED4F37B327355D1600B47391 /* CustomPresentationController */ = { + isa = PBXNativeTarget; + buildConfigurationList = ED4F37C827355D1700B47391 /* Build configuration list for PBXNativeTarget "CustomPresentationController" */; + buildPhases = ( + ED4F37B027355D1600B47391 /* Sources */, + ED4F37B127355D1600B47391 /* Frameworks */, + ED4F37B227355D1600B47391 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = CustomPresentationController; + productName = CustomPresentationController; + productReference = ED4F37B427355D1600B47391 /* CustomPresentationController.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + ED4F37AC27355D1600B47391 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1310; + LastUpgradeCheck = 1310; + TargetAttributes = { + ED4F37B327355D1600B47391 = { + CreatedOnToolsVersion = 13.1; + }; + }; + }; + buildConfigurationList = ED4F37AF27355D1600B47391 /* Build configuration list for PBXProject "CustomPresentationController" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = ED4F37AB27355D1600B47391; + productRefGroup = ED4F37B527355D1600B47391 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + ED4F37B327355D1600B47391 /* CustomPresentationController */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + ED4F37B227355D1600B47391 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ED4F37C427355D1700B47391 /* LaunchScreen.storyboard in Resources */, + ED4F37C127355D1700B47391 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + ED4F37B027355D1600B47391 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ED4F37CF273560F300B47391 /* ContentViewController.swift in Sources */, + ED8F4199273AF44C0080C26F /* VisualEffectsViewController.swift in Sources */, + ED4F37BC27355D1600B47391 /* ViewController.swift in Sources */, + ED8F419B273B06BC0080C26F /* MotionEffectsViewController.swift in Sources */, + ED4F37D12735BC2600B47391 /* DialogTransitionManager.swift in Sources */, + ED4F37B827355D1600B47391 /* AppDelegate.swift in Sources */, + ED4F37CD2735608E00B47391 /* ShelfTransitionManager.swift in Sources */, + ED4F37BA27355D1600B47391 /* SceneDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + ED4F37C227355D1700B47391 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + ED4F37C327355D1700B47391 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + ED4F37C627355D1700B47391 /* 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++17"; + 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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + 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 = 15.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; + }; + ED4F37C727355D1700B47391 /* 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++17"; + 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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + 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 = 15.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; + }; + ED4F37C927355D1700B47391 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = CustomPresentationController/Info.plist; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = ios.example.CustomPresentationController; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + ED4F37CA27355D1700B47391 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = CustomPresentationController/Info.plist; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = ios.example.CustomPresentationController; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + ED4F37AF27355D1600B47391 /* Build configuration list for PBXProject "CustomPresentationController" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + ED4F37C627355D1700B47391 /* Debug */, + ED4F37C727355D1700B47391 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + ED4F37C827355D1700B47391 /* Build configuration list for PBXNativeTarget "CustomPresentationController" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + ED4F37C927355D1700B47391 /* Debug */, + ED4F37CA27355D1700B47391 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = ED4F37AC27355D1600B47391 /* Project object */; +} diff --git a/CustomPresentationController/CustomPresentationController.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/CustomPresentationController/CustomPresentationController.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/CustomPresentationController/CustomPresentationController.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/CustomPresentationController/CustomPresentationController.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/CustomPresentationController/CustomPresentationController.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/CustomPresentationController/CustomPresentationController.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/CustomPresentationController/CustomPresentationController/AppDelegate.swift b/CustomPresentationController/CustomPresentationController/AppDelegate.swift new file mode 100644 index 0000000..0ec1b7e --- /dev/null +++ b/CustomPresentationController/CustomPresentationController/AppDelegate.swift @@ -0,0 +1,36 @@ +// +// AppDelegate.swift +// CustomPresentationController +// +// Created by Thomas Asheim Smedmann on 05/11/2021. +// + +import UIKit + +@main +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/CustomPresentationController/CustomPresentationController/Assets.xcassets/AccentColor.colorset/Contents.json b/CustomPresentationController/CustomPresentationController/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/CustomPresentationController/CustomPresentationController/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/CustomPresentationController/CustomPresentationController/Assets.xcassets/AppIcon.appiconset/Contents.json b/CustomPresentationController/CustomPresentationController/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..9221b9b --- /dev/null +++ b/CustomPresentationController/CustomPresentationController/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/CustomPresentationController/CustomPresentationController/Assets.xcassets/Contents.json b/CustomPresentationController/CustomPresentationController/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/CustomPresentationController/CustomPresentationController/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/CustomPresentationController/CustomPresentationController/Assets.xcassets/fruit.imageset/Contents.json b/CustomPresentationController/CustomPresentationController/Assets.xcassets/fruit.imageset/Contents.json new file mode 100644 index 0000000..a5a6554 --- /dev/null +++ b/CustomPresentationController/CustomPresentationController/Assets.xcassets/fruit.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "fruit.jpeg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/CustomPresentationController/CustomPresentationController/Assets.xcassets/fruit.imageset/fruit.jpeg b/CustomPresentationController/CustomPresentationController/Assets.xcassets/fruit.imageset/fruit.jpeg new file mode 100644 index 0000000..8010eb2 Binary files /dev/null and b/CustomPresentationController/CustomPresentationController/Assets.xcassets/fruit.imageset/fruit.jpeg differ diff --git a/CustomPresentationController/CustomPresentationController/Base.lproj/LaunchScreen.storyboard b/CustomPresentationController/CustomPresentationController/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..865e932 --- /dev/null +++ b/CustomPresentationController/CustomPresentationController/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CustomPresentationController/CustomPresentationController/ContentViewController.swift b/CustomPresentationController/CustomPresentationController/ContentViewController.swift new file mode 100644 index 0000000..ba41a38 --- /dev/null +++ b/CustomPresentationController/CustomPresentationController/ContentViewController.swift @@ -0,0 +1,78 @@ +// +// ContentViewController.swift +// CustomPresentationController +// +// Created by Thomas Asheim Smedmann on 05/11/2021. +// + +import UIKit + +class ContentViewController: UIViewController { + private let titleLabel: UILabel = { + let label = UILabel() + label.text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + label.numberOfLines = 0 + label.font = .systemFont(ofSize: 20) + label.setContentHuggingPriority(.defaultLow, for: .horizontal) + label.setContentHuggingPriority(.defaultHigh, for: .vertical) + label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + label.setContentCompressionResistancePriority(.defaultHigh, for: .vertical) + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + private let dismissButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Dismiss", for: .normal) + button.setContentHuggingPriority(.defaultHigh, for: .horizontal) + button.setContentHuggingPriority(.defaultHigh, for: .vertical) + button.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) + button.setContentCompressionResistancePriority(.defaultHigh, for: .vertical) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private let contentLabel: UILabel = { + let label = UILabel() + label.text = """ + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam malesuada vulputate mauris, vitae tristique lacus rhoncus id. Morbi mi leo, scelerisque et faucibus nec, egestas semper dolor. Etiam rutrum diam sit amet quam aliquet, vel vulputate ex blandit. Nam porta pretium ligula vel rutrum. Cras ultricies purus sit amet iaculis congue. Fusce pulvinar velit semper dolor sagittis vulputate. Vivamus ut sapien a tellus finibus pharetra. Sed at massa turpis. Nullam venenatis sodales nibh vel tempor. Cras pulvinar dui vel justo egestas, et viverra felis tincidunt. Suspendisse luctus elementum est, et venenatis est rhoncus at. Nulla maximus elit nisl, et congue nisl aliquet id. + """ + label.numberOfLines = 0 + label.font = .systemFont(ofSize: 17) + label.setContentHuggingPriority(.defaultLow, for: .vertical) + label.setContentHuggingPriority(.defaultLow, for: .horizontal) + label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + label.setContentCompressionResistancePriority(.defaultLow, for: .vertical) + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = .systemBackground + view.addSubview(titleLabel) + view.addSubview(dismissButton) + view.addSubview(contentLabel) + + NSLayoutConstraint.activate([ + titleLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + titleLabel.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16), + + dismissButton.firstBaselineAnchor.constraint(equalTo: titleLabel.firstBaselineAnchor), + dismissButton.leadingAnchor.constraint(equalTo: titleLabel.trailingAnchor, constant: 16), + dismissButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16), + + contentLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 16), + contentLabel.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16), + contentLabel.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16), + contentLabel.bottomAnchor.constraint(lessThanOrEqualTo: view.safeAreaLayoutGuide.bottomAnchor) + ]) + + dismissButton.addTarget(self, action: #selector(onDismiss), for: .primaryActionTriggered) + } + + @objc private func onDismiss() { + dismiss(animated: true) + } +} diff --git a/CustomPresentationController/CustomPresentationController/DialogTransitionManager.swift b/CustomPresentationController/CustomPresentationController/DialogTransitionManager.swift new file mode 100644 index 0000000..ef2d6f9 --- /dev/null +++ b/CustomPresentationController/CustomPresentationController/DialogTransitionManager.swift @@ -0,0 +1,190 @@ +// +// DialogTransitionManager.swift +// CustomPresentationController +// +// Created by Thomas Asheim Smedmann on 05/11/2021. +// + +import UIKit + +// MARK: DialogPresentationController + +final class DialogPresentationController: UIPresentationController { + private let backgroundView: UIView = { + let view = UIView() + view.backgroundColor = .black + view.alpha = 0 + return view + }() + + private lazy var tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(onTap)) + + @objc private func onTap(_ gesture: UITapGestureRecognizer) { + guard + let presentedView = presentedView, + let containerView = containerView, + !presentedView.frame.contains(gesture.location(in: containerView)) + else { return } + + presentedViewController.dismiss(animated: true) + } +} + +// MARK: UIPresentationController + +extension DialogPresentationController { + override var frameOfPresentedViewInContainerView: CGRect { + guard let containerView = containerView else { return .zero } + let frame = containerView.frame + var width = min(frame.width, frame.height) + width = width * 4 / 6 + let paddingLeft = (frame.width - width) / 2 + let paddingTop = (frame.height - width) / 2 + return CGRect(x: frame.minX + paddingLeft, y: frame.minY + paddingTop, width: width, height: width) + } + + override func presentationTransitionWillBegin() { + guard let presentedView = presentedView else { return } + + presentedView.layer.cornerRadius = 10 + presentedView.layer.shadowColor = UIColor.black.cgColor + presentedView.layer.shadowOffset = .zero + presentedView.layer.shadowOpacity = 0.3 + presentedView.layer.shadowRadius = 10 + presentedView.clipsToBounds = false + + guard let containerView = containerView else { return } + + containerView.addGestureRecognizer(tapGestureRecognizer) + + containerView.addSubview(backgroundView) + backgroundView.frame = containerView.frame + + guard let transitionCoordinator = presentingViewController.transitionCoordinator else { return } + + transitionCoordinator.animate(alongsideTransition: { context in + self.backgroundView.alpha = 0.3 + }) + } + + override func presentationTransitionDidEnd(_ completed: Bool) { + if !completed { + backgroundView.removeFromSuperview() + containerView?.removeGestureRecognizer(tapGestureRecognizer) + } + } + + override func dismissalTransitionWillBegin() { + guard let transitionCoordinator = presentingViewController.transitionCoordinator else { return } + + transitionCoordinator.animate(alongsideTransition: { context in + self.backgroundView.alpha = 0 + }) + } + + override func dismissalTransitionDidEnd(_ completed: Bool) { + if completed { + backgroundView.removeFromSuperview() + containerView?.removeGestureRecognizer(tapGestureRecognizer) + } + } + + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + guard + let containerView = containerView, + let presentedView = presentedView + else { return } + coordinator.animate(alongsideTransition: { context in + self.backgroundView.frame = containerView.frame + presentedView.frame = self.frameOfPresentedViewInContainerView + }) + } +} + +// MARK: DialogTransitionManager + +final class DialogTransitionManager: NSObject, UIViewControllerTransitioningDelegate { + func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { + return DialogPresentationController(presentedViewController: presented, presenting: presenting) + } + + func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { + return DialogAnimationController(animationType: .present) + } + + func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { + return DialogAnimationController(animationType: .dismiss) + } +} + +// MARK: UIViewControllerAnimatedTransitioning + +final class DialogAnimationController: NSObject, UIViewControllerAnimatedTransitioning { + enum AnimationType { + case present + case dismiss + } + + private let animationType: AnimationType + private let animationDuration: TimeInterval + + init(animationType: AnimationType, animationDuration: TimeInterval = 0.3) { + self.animationType = animationType + self.animationDuration = animationDuration + } + + func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { + return animationDuration + } + + func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + switch animationType { + case .present: + guard + let toViewController = transitionContext.viewController(forKey: .to), + let toView = transitionContext.view(forKey: .to) + else { + return transitionContext.completeTransition(false) + } + transitionContext.containerView.addSubview(toView) + presentAnimation(with: transitionContext, and: toViewController, animating: toView) + case .dismiss: + guard + let fromView = transitionContext.view(forKey: .from) + else { + return transitionContext.completeTransition(false) + } + transitionContext.containerView.addSubview(fromView) + dismissAnimation(with: transitionContext, animating: fromView) + } + } + + private func presentAnimation(with transitionContext: UIViewControllerContextTransitioning, + and viewController: UIViewController, + animating view: UIView) { + let finalFrame = transitionContext.finalFrame(for: viewController) + view.frame = finalFrame + view.transform = CGAffineTransform(scaleX: 0.2, y: 0.2) + view.alpha = 0 + let propertyAnimator = UIViewPropertyAnimator(duration: transitionDuration(using: transitionContext), curve: .easeOut, animations: { + view.transform = .identity + view.alpha = 1 + }) + propertyAnimator.addCompletion({ _ in + transitionContext.completeTransition(!transitionContext.transitionWasCancelled) + }) + propertyAnimator.startAnimation() + } + + private func dismissAnimation(with transitionContext: UIViewControllerContextTransitioning, + animating view: UIView) { + let propertyAnimator = UIViewPropertyAnimator(duration: transitionDuration(using: transitionContext), curve: .easeOut, animations: { + view.transform = CGAffineTransform(scaleX: 0.2, y: 0.2) + view.alpha = 0 + }) + propertyAnimator.addCompletion({ _ in + transitionContext.completeTransition(!transitionContext.transitionWasCancelled) + }) + propertyAnimator.startAnimation() + } +} diff --git a/CustomPresentationController/CustomPresentationController/Info.plist b/CustomPresentationController/CustomPresentationController/Info.plist new file mode 100644 index 0000000..0eb786d --- /dev/null +++ b/CustomPresentationController/CustomPresentationController/Info.plist @@ -0,0 +1,23 @@ + + + + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + + + diff --git a/CustomPresentationController/CustomPresentationController/MotionEffectsViewController.swift b/CustomPresentationController/CustomPresentationController/MotionEffectsViewController.swift new file mode 100644 index 0000000..38595d7 --- /dev/null +++ b/CustomPresentationController/CustomPresentationController/MotionEffectsViewController.swift @@ -0,0 +1,96 @@ +// +// MotionEffectsViewController.swift +// CustomPresentationController +// +// Created by Thomas Asheim Smedmann on 09/11/2021. +// + +import UIKit + +class MotionEffectsViewController: UIViewController { + private lazy var stackView: UIStackView = { + let stackView = UIStackView(arrangedSubviews: [ + topLabel, + middleLabel, + bottomLabel + ]) + stackView.axis = .vertical + stackView.distribution = .fillEqually + stackView.alignment = .center + stackView.translatesAutoresizingMaskIntoConstraints = false + return stackView + }() + + private let topLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 20) + label.text = "I move with motion" + label.textColor = .systemBlue + let horizontalMotionEffect = UIInterpolatingMotionEffect(keyPath: "layer.transform.translation.x", type: .tiltAlongHorizontalAxis) + let verticalMotionEffect = UIInterpolatingMotionEffect(keyPath: "layer.transform.translation.y", type: .tiltAlongVerticalAxis) + horizontalMotionEffect.minimumRelativeValue = 100 + horizontalMotionEffect.maximumRelativeValue = -100 + verticalMotionEffect.minimumRelativeValue = 100 + verticalMotionEffect.maximumRelativeValue = -100 + let motionEffectGroup = UIMotionEffectGroup() + motionEffectGroup.motionEffects = [horizontalMotionEffect, verticalMotionEffect] + label.addMotionEffect(motionEffectGroup) + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + private let middleLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 20) + label.text = "My shadow move with motion" + label.textColor = .label + label.layer.shadowColor = UIColor.black.cgColor + label.layer.shadowRadius = 10 + label.layer.shadowOpacity = 1 + label.layer.shadowOffset = .zero + let horizontalMotionEffect = UIInterpolatingMotionEffect(keyPath: "layer.shadowOffset.width", type: .tiltAlongHorizontalAxis) + let verticalMotionEffect = UIInterpolatingMotionEffect(keyPath: "layer.shadowOffset.height", type: .tiltAlongVerticalAxis) + horizontalMotionEffect.minimumRelativeValue = 100 + horizontalMotionEffect.maximumRelativeValue = -100 + verticalMotionEffect.minimumRelativeValue = 100 + verticalMotionEffect.maximumRelativeValue = -100 + let motionEffectGroup = UIMotionEffectGroup() + motionEffectGroup.motionEffects = [horizontalMotionEffect, verticalMotionEffect] + label.addMotionEffect(motionEffectGroup) + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + private let bottomLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 20) + label.text = "I move with motion" + label.textColor = .systemRed + let horizontalMotionEffect = UIInterpolatingMotionEffect(keyPath: "center.x", type: .tiltAlongHorizontalAxis) + let verticalMotionEffect = UIInterpolatingMotionEffect(keyPath: "center.y", type: .tiltAlongVerticalAxis) + horizontalMotionEffect.minimumRelativeValue = 100 + horizontalMotionEffect.maximumRelativeValue = -100 + verticalMotionEffect.minimumRelativeValue = 100 + verticalMotionEffect.maximumRelativeValue = -100 + let motionEffectGroup = UIMotionEffectGroup() + motionEffectGroup.motionEffects = [horizontalMotionEffect, verticalMotionEffect] + label.addMotionEffect(motionEffectGroup) + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = .systemBackground + + view.addSubview(stackView) + + NSLayoutConstraint.activate([ + stackView.topAnchor.constraint(equalTo: view.topAnchor), + stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + } +} diff --git a/CustomPresentationController/CustomPresentationController/SceneDelegate.swift b/CustomPresentationController/CustomPresentationController/SceneDelegate.swift new file mode 100644 index 0000000..4aa873a --- /dev/null +++ b/CustomPresentationController/CustomPresentationController/SceneDelegate.swift @@ -0,0 +1,58 @@ +// +// SceneDelegate.swift +// CustomPresentationController +// +// Created by Thomas Asheim Smedmann on 05/11/2021. +// + +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 windowScene = (scene as? UIWindowScene) else { return } + + let window = UIWindow(windowScene: windowScene) + window.rootViewController = ViewController() + window.makeKeyAndVisible() + + self.window = window + } + + 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 necessarily 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/CustomPresentationController/CustomPresentationController/ShelfTransitionManager.swift b/CustomPresentationController/CustomPresentationController/ShelfTransitionManager.swift new file mode 100644 index 0000000..59e2765 --- /dev/null +++ b/CustomPresentationController/CustomPresentationController/ShelfTransitionManager.swift @@ -0,0 +1,207 @@ +// +// ShelfTransitionManager.swift +// CustomPresentationController +// +// Created by Thomas Asheim Smedmann on 05/11/2021. +// + +import UIKit + +// MARK: ShelfPresentationController + +final class ShelfPresentationController: UIPresentationController { + private let backgroundView: UIView = { + let view = UIView() + view.backgroundColor = .black + view.alpha = 0 + return view + }() + + private lazy var panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(onPan)) + private lazy var tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(onTap)) + + let shelfDismissAnimationController = ShelfDismissAnimationController() + + @objc private func onPan(_ gesture: UIPanGestureRecognizer) { + guard let presentedView = presentedView else { return } + let translation = gesture.translation(in: presentedView) + let progress = min(max(translation.y / presentedView.frame.height, 0), 1) + switch gesture.state { + case .began: + shelfDismissAnimationController.update(progress) + presentedViewController.dismiss(animated: true) + case .changed: + shelfDismissAnimationController.update(progress) + case .ended, .cancelled: + let velocity = gesture.velocity(in: presentedView) + if progress > 0.5 && velocity.y > 0 { + shelfDismissAnimationController.finish() + } else { + shelfDismissAnimationController.cancel() + } + default: + return + } + } + + @objc private func onTap(_ gesture: UITapGestureRecognizer) { + guard + let presentedView = presentedView, + let containerView = containerView, + !presentedView.frame.contains(gesture.location(in: containerView)) + else { return } + + presentedViewController.dismiss(animated: true) + } +} + +// MARK: UIPresentationController + +extension ShelfPresentationController { + override var frameOfPresentedViewInContainerView: CGRect { + guard let containerView = containerView else { return .zero } + let frame = containerView.frame + let height = containerView.frame.height / 2 + return CGRect(x: frame.minX, y: frame.minY + height, width: frame.width, height: height) + } + + override func presentationTransitionWillBegin() { + guard let presentedView = presentedView else { return } + + presentedView.addGestureRecognizer(panGestureRecognizer) + + presentedView.layer.cornerRadius = 10 + presentedView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] + presentedView.layer.shadowColor = UIColor.black.cgColor + presentedView.layer.shadowOffset = .zero + presentedView.layer.shadowOpacity = 0.3 + presentedView.layer.shadowRadius = 10 + presentedView.clipsToBounds = false + + guard let containerView = containerView else { return } + + containerView.addGestureRecognizer(tapGestureRecognizer) + + containerView.addSubview(backgroundView) + backgroundView.frame = containerView.frame + + guard let transitionCoordinator = presentingViewController.transitionCoordinator else { return } + + transitionCoordinator.animate(alongsideTransition: { context in + self.backgroundView.alpha = 0.3 + }) + } + + override func presentationTransitionDidEnd(_ completed: Bool) { + if !completed { + backgroundView.removeFromSuperview() + presentedView?.removeGestureRecognizer(panGestureRecognizer) + containerView?.removeGestureRecognizer(tapGestureRecognizer) + } + } + + override func dismissalTransitionWillBegin() { + guard let transitionCoordinator = presentingViewController.transitionCoordinator else { return } + + transitionCoordinator.animate(alongsideTransition: { context in + self.backgroundView.alpha = 0 + }) + } + + override func dismissalTransitionDidEnd(_ completed: Bool) { + if completed { + backgroundView.removeFromSuperview() + presentedView?.removeGestureRecognizer(panGestureRecognizer) + containerView?.removeGestureRecognizer(tapGestureRecognizer) + } + } + + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + guard + let containerView = containerView, + let presentedView = presentedView + else { return } + coordinator.animate(alongsideTransition: { context in + self.backgroundView.frame = containerView.frame + presentedView.frame = self.frameOfPresentedViewInContainerView + }) + } +} + +// MARK: ShelfTransitionManager + +final class ShelfTransitionManager: NSObject, UIViewControllerTransitioningDelegate { + func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { + return ShelfPresentationController(presentedViewController: presented, presenting: presenting ?? source) + } + + func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { + guard let shelfPresentationController = dismissed.presentationController as? ShelfPresentationController else { return nil } + return shelfPresentationController.shelfDismissAnimationController + } + + func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { + guard let shelfDismissAnimationController = animator as? ShelfDismissAnimationController else { return nil } + if shelfDismissAnimationController.interactiveDismissal { + return shelfDismissAnimationController + } + return nil + } +} + +// MARK: ShelfDismissAnimationController + +final class ShelfDismissAnimationController: UIPercentDrivenInteractiveTransition { + private(set) var interactiveDismissal: Bool = false + + override func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) { + super.startInteractiveTransition(transitionContext) + } + + override func update(_ percentComplete: CGFloat) { + super.update(percentComplete) + interactiveDismissal = true + } + + override func cancel() { + super.cancel() + interactiveDismissal = false + } + + override func finish() { + super.finish() + interactiveDismissal = false + } +} + +extension ShelfDismissAnimationController: UIViewControllerAnimatedTransitioning { + func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { + return 0.3 + } + + func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + interruptibleAnimator(using: transitionContext).startAnimation() + } + + func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating { + let propertyAnimator = UIViewPropertyAnimator(duration: transitionDuration(using: transitionContext), curve: .easeOut) + propertyAnimator.addCompletion({ _ in + transitionContext.completeTransition(!transitionContext.transitionWasCancelled) + }) + + guard + let fromView = transitionContext.view(forKey: .from) + else { + return propertyAnimator + } + + transitionContext.containerView.addSubview(fromView) + + let height = fromView.frame.height + propertyAnimator.addAnimations { + fromView.frame.origin.y += height + } + + return propertyAnimator + } +} diff --git a/CustomPresentationController/CustomPresentationController/ViewController.swift b/CustomPresentationController/CustomPresentationController/ViewController.swift new file mode 100644 index 0000000..bc8c04d --- /dev/null +++ b/CustomPresentationController/CustomPresentationController/ViewController.swift @@ -0,0 +1,199 @@ +// +// ViewController.swift +// CustomPresentationController +// +// Created by Thomas Asheim Smedmann on 05/11/2021. +// + +import UIKit + +class ViewController: UIViewController { + private lazy var stackView: UIStackView = { + let stackView = UIStackView(arrangedSubviews: [ + presentVisualEffectsViewButton, + presentMotionEffectsViewButton, + presentAlertAlertButton, + presentAlertSheetButton, + presentModalButton, + presentPopoverButton, + presentShelfButton, + presentDialogButton, + presentCustomShelfButton, + presentCustomDialogButton + ]) + stackView.axis = .vertical + stackView.distribution = .fillEqually + stackView.alignment = .center + stackView.translatesAutoresizingMaskIntoConstraints = false + return stackView + }() + + private let presentVisualEffectsViewButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Visual effects", for: .normal) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private let presentMotionEffectsViewButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Motion effects", for: .normal) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private let presentAlertAlertButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Alert alert", for: .normal) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private let presentAlertSheetButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Alert sheet", for: .normal) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private let presentModalButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Modal", for: .normal) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private let presentPopoverButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Popover", for: .normal) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private let presentShelfButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Shelf", for: .normal) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private let presentDialogButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Dialog", for: .normal) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private let presentCustomShelfButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Custom shelf", for: .normal) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private let presentCustomDialogButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Custom dialog", for: .normal) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private let shelfTransitionManager = ShelfTransitionManager() + private let dialogTransitionManager = DialogTransitionManager() + + init() { + super.init(nibName: nil, bundle: nil) + configure() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func configure() { + view.backgroundColor = .systemBackground + + view.addSubview(stackView) + + NSLayoutConstraint.activate([ + stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + stackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), + stackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), + stackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) + ]) + + presentVisualEffectsViewButton.addTarget(self, action: #selector(showVisualEffectsView), for: .primaryActionTriggered) + presentMotionEffectsViewButton.addTarget(self, action: #selector(showMotionEffectsView), for: .primaryActionTriggered) + presentAlertAlertButton.addTarget(self, action: #selector(showAlertAlert), for: .primaryActionTriggered) + presentAlertSheetButton.addTarget(self, action: #selector(showAlertSheet), for: .primaryActionTriggered) + presentModalButton.addTarget(self, action: #selector(showModal), for: .primaryActionTriggered) + presentPopoverButton.addTarget(self, action: #selector(showPopover), for: .primaryActionTriggered) + presentShelfButton.addTarget(self, action: #selector(showShelf), for: .primaryActionTriggered) + presentDialogButton.addTarget(self, action: #selector(showDialog), for: .primaryActionTriggered) + presentCustomShelfButton.addTarget(self, action: #selector(showCustomShelf), for: .primaryActionTriggered) + presentCustomDialogButton.addTarget(self, action: #selector(showCustomDialog), for: .primaryActionTriggered) + } + + @objc private func showVisualEffectsView() { + let visualEffectsViewController = VisualEffectsViewController() + visualEffectsViewController.modalPresentationStyle = .pageSheet + present(visualEffectsViewController, animated: true) + } + + @objc private func showMotionEffectsView() { + let motionEffectsViewController = MotionEffectsViewController() + motionEffectsViewController.modalPresentationStyle = .pageSheet + present(motionEffectsViewController, animated: true) + } + + @objc private func showAlertAlert() { + let alert = UIAlertController(title: "Some alert title", message: "Some alert message", preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "Ok", style: .default)) + present(alert, animated: true) + } + + @objc private func showAlertSheet() { + let alert = UIAlertController(title: "Some alert title", message: "Some alert message", preferredStyle: .actionSheet) + alert.addAction(UIAlertAction(title: "Ok", style: .default)) + present(alert, animated: true) + } + + @objc private func showModal() { + let contentViewController = ContentViewController() + contentViewController.modalPresentationStyle = .currentContext + present(contentViewController, animated: true) + } + + @objc private func showPopover() { + let contentViewController = ContentViewController() + contentViewController.modalPresentationStyle = .popover + present(contentViewController, animated: true) + } + + @objc private func showShelf() { + let contentViewController = ContentViewController() + contentViewController.modalPresentationStyle = .pageSheet + present(contentViewController, animated: true) + } + + @objc private func showDialog() { + let contentViewController = ContentViewController() + contentViewController.modalPresentationStyle = .formSheet + present(contentViewController, animated: true) + } + + @objc private func showCustomShelf() { + let contentViewController = ContentViewController() + contentViewController.modalPresentationStyle = .custom + contentViewController.transitioningDelegate = shelfTransitionManager + present(contentViewController, animated: true) + } + + @objc private func showCustomDialog() { + let contentViewController = ContentViewController() + contentViewController.modalPresentationStyle = .custom + contentViewController.transitioningDelegate = dialogTransitionManager + present(contentViewController, animated: true) + } +} + diff --git a/CustomPresentationController/CustomPresentationController/VisualEffectsViewController.swift b/CustomPresentationController/CustomPresentationController/VisualEffectsViewController.swift new file mode 100644 index 0000000..01c99e9 --- /dev/null +++ b/CustomPresentationController/CustomPresentationController/VisualEffectsViewController.swift @@ -0,0 +1,148 @@ +// +// VisualEffectsViewController.swift +// CustomPresentationController +// +// Created by Thomas Asheim Smedmann on 09/11/2021. +// + +import UIKit + +class VisualEffectsViewController: UIViewController { + private let imageView: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "fruit")!) + imageView.contentMode = .scaleAspectFill + imageView.translatesAutoresizingMaskIntoConstraints = false + return imageView + }() + + private lazy var stackView: UIStackView = { + let stackView = UIStackView(arrangedSubviews: [ + systemThinMaterialBlurEffectView, + systemMaterialBlurEffectView, + systemThickMaterialBlurEffectView, + systemChromeMaterialBlurEffectView + ]) + stackView.distribution = .fillEqually + stackView.alignment = .fill + stackView.axis = .vertical + stackView.spacing = 32 + stackView.backgroundColor = .clear + stackView.translatesAutoresizingMaskIntoConstraints = false + return stackView + }() + + private let systemThinMaterialBlurEffectView: UIVisualEffectView = { + let visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .systemThinMaterial)) + let label = UILabel() + label.font = .systemFont(ofSize: 15) + label.text = ".systemThinMaterial" + label.translatesAutoresizingMaskIntoConstraints = false + let view = UIView() + view.addSubview(label) + view.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + label.centerXAnchor.constraint(equalTo: view.centerXAnchor), + label.centerYAnchor.constraint(equalTo: view.centerYAnchor), + ]) + visualEffectView.contentView.addSubview(view) + NSLayoutConstraint.activate([ + view.topAnchor.constraint(equalTo: visualEffectView.contentView.topAnchor), + view.leadingAnchor.constraint(equalTo: visualEffectView.contentView.leadingAnchor), + view.trailingAnchor.constraint(equalTo: visualEffectView.contentView.trailingAnchor), + view.bottomAnchor.constraint(equalTo: visualEffectView.contentView.bottomAnchor) + ]) + visualEffectView.translatesAutoresizingMaskIntoConstraints = false + return visualEffectView + }() + + private let systemMaterialBlurEffectView: UIVisualEffectView = { + let visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .systemMaterial)) + let label = UILabel() + label.font = .systemFont(ofSize: 15) + label.text = ".systemMaterial" + label.translatesAutoresizingMaskIntoConstraints = false + let view = UIView() + view.addSubview(label) + view.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + label.centerXAnchor.constraint(equalTo: view.centerXAnchor), + label.centerYAnchor.constraint(equalTo: view.centerYAnchor), + ]) + visualEffectView.contentView.addSubview(view) + NSLayoutConstraint.activate([ + view.topAnchor.constraint(equalTo: visualEffectView.contentView.topAnchor), + view.leadingAnchor.constraint(equalTo: visualEffectView.contentView.leadingAnchor), + view.trailingAnchor.constraint(equalTo: visualEffectView.contentView.trailingAnchor), + view.bottomAnchor.constraint(equalTo: visualEffectView.contentView.bottomAnchor) + ]) + visualEffectView.translatesAutoresizingMaskIntoConstraints = false + return visualEffectView + }() + + private let systemThickMaterialBlurEffectView: UIVisualEffectView = { + let visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .systemThickMaterial)) + let label = UILabel() + label.font = .systemFont(ofSize: 15) + label.text = ".systemThickMaterial" + label.translatesAutoresizingMaskIntoConstraints = false + let view = UIView() + view.addSubview(label) + view.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + label.centerXAnchor.constraint(equalTo: view.centerXAnchor), + label.centerYAnchor.constraint(equalTo: view.centerYAnchor), + ]) + visualEffectView.contentView.addSubview(view) + NSLayoutConstraint.activate([ + view.topAnchor.constraint(equalTo: visualEffectView.contentView.topAnchor), + view.leadingAnchor.constraint(equalTo: visualEffectView.contentView.leadingAnchor), + view.trailingAnchor.constraint(equalTo: visualEffectView.contentView.trailingAnchor), + view.bottomAnchor.constraint(equalTo: visualEffectView.contentView.bottomAnchor) + ]) + visualEffectView.translatesAutoresizingMaskIntoConstraints = false + return visualEffectView + }() + + private let systemChromeMaterialBlurEffectView: UIVisualEffectView = { + let visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .systemChromeMaterial)) + let label = UILabel() + label.font = .systemFont(ofSize: 15) + label.text = ".systemChromeMaterial" + label.translatesAutoresizingMaskIntoConstraints = false + let view = UIView() + view.addSubview(label) + view.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + label.centerXAnchor.constraint(equalTo: view.centerXAnchor), + label.centerYAnchor.constraint(equalTo: view.centerYAnchor), + ]) + visualEffectView.contentView.addSubview(view) + NSLayoutConstraint.activate([ + view.topAnchor.constraint(equalTo: visualEffectView.contentView.topAnchor), + view.leadingAnchor.constraint(equalTo: visualEffectView.contentView.leadingAnchor), + view.trailingAnchor.constraint(equalTo: visualEffectView.contentView.trailingAnchor), + view.bottomAnchor.constraint(equalTo: visualEffectView.contentView.bottomAnchor) + ]) + visualEffectView.translatesAutoresizingMaskIntoConstraints = false + return visualEffectView + }() + + override func viewDidLoad() { + super.viewDidLoad() + + view.addSubview(imageView) + view.addSubview(stackView) + + NSLayoutConstraint.activate([ + imageView.topAnchor.constraint(equalTo: view.topAnchor), + imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + + stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 32), + stackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 32), + stackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -32), + stackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -32) + ]) + } +} diff --git a/CustomPresentationController/README.md b/CustomPresentationController/README.md new file mode 100644 index 0000000..8929310 --- /dev/null +++ b/CustomPresentationController/README.md @@ -0,0 +1,3 @@ +# Custom Presentation Controller + +A simple example on how to create your very own [UIPresentationController](https://developer.apple.com/documentation/uikit/uipresentationcontroller). diff --git a/README.md b/README.md new file mode 100644 index 0000000..31cedfe --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# iOS Examples + +A collection of iOS examples. + +- [Custom Interface Builder View](CustomInterfaceBuilderView) +- [Custom Presentation Controller](CustomPresentationController) + +These examples are created as a way to learn. Feel free to use them as you please. +Feedback are also welcome. + +Happy coding! :smile: