├── .gitignore ├── Previews ├── EmptyGridView.gif ├── GalleryAccess.jpg ├── LoadingView.gif ├── NiceButton.gif ├── NotificationsPermission.jpg ├── NotificationsPermission2.jpg ├── NotificationsPermission3.jpg └── Particle.gif ├── README.md ├── UIExamples.xcodeproj └── project.pbxproj └── UIExamples ├── App ├── AppDelegate.swift ├── Base.lproj │ └── LaunchScreen.storyboard ├── Info.plist └── SceneDelegate.swift ├── Modules ├── EmptyGrid │ ├── Model │ │ └── GridPosition.swift │ └── View │ │ ├── EmptyGridView.swift │ │ ├── GridCell.swift │ │ └── GridView.swift ├── ExamplesList │ ├── Cell │ │ └── ExampleCell.swift │ ├── ExamplesListController.swift │ ├── Model │ │ └── ExampleListModel.swift │ └── View │ │ └── ExamplesListView.swift ├── GalleryAccess │ └── View │ │ └── GalleryAccessView.swift ├── Loading │ ├── Model │ │ └── LoadingParticle.swift │ └── View │ │ ├── LoadingView.swift │ │ └── StarrySkyView.swift ├── MagicButton │ ├── Model │ │ └── ButtonParticle.swift │ ├── Shared │ │ ├── ParticleAnimator.swift │ │ ├── ParticleStyleBounce.swift │ │ └── ProgressConverter.swift │ ├── Style │ │ └── MagicStyle.swift │ ├── Subview │ │ ├── ButtonParticleView.swift │ │ └── CustomShape.swift │ └── View │ │ └── MagicView.swift ├── NiceButton │ ├── NiceButtonStyle │ │ └── NiceButtonStyle.swift │ └── View │ │ └── NiceButtonExample.swift └── Notifications │ ├── Model │ └── NotificationSetupModel.swift │ ├── Subview │ ├── NotificationPushImageView.swift │ └── NotificationPushView.swift │ ├── View1 │ └── NotificationSetupView.swift │ ├── View2 │ └── NotificationSetupView2.swift │ └── View3 │ └── NotificationSetupView3.swift └── Shared ├── Extentions ├── UIColor+Extentions.swift └── View+Builders.swift ├── Models ├── NotificationFeature.swift ├── NotificationPushModel.swift └── SwiftUIExample.swift ├── Resources ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── app-photos.imageset │ │ ├── Contents.json │ │ ├── app-photos.png │ │ ├── app-photos@2x.png │ │ └── app-photos@3x.png │ ├── notification-content-image-2.imageset │ │ ├── Contents.json │ │ └── notification-content-image-2.png │ ├── notification-content-image.imageset │ │ ├── Contents.json │ │ └── notification-content-image.png │ ├── notification-push-app-icon.imageset │ │ ├── Contents.json │ │ └── notification-push-app-icon.png │ ├── notification-push-unknown.imageset │ │ ├── Contents.json │ │ ├── No App Icon.png │ │ ├── No App Icon@2x.png │ │ └── No App Icon@3x.png │ └── notification-push-user.imageset │ │ ├── Contents.json │ │ └── notification-push-user.jpg └── Strings │ └── en.lproj │ └── Localizable.strings └── Views └── CircleSmallButton.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | *.DS_Store 9 | */.DS_Store 10 | *.iml 11 | .gradle 12 | .idea 13 | captures 14 | .externalNativeBuild 15 | .cxx 16 | local.properties 17 | 18 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 19 | *.xcscmblueprint 20 | *.xccheckout 21 | 22 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 23 | build/ 24 | DerivedData/ 25 | *.moved-aside 26 | *.pbxuser 27 | !default.pbxuser 28 | *.mode1v3 29 | !default.mode1v3 30 | *.mode2v3 31 | !default.mode2v3 32 | *.perspectivev3 33 | !default.perspectivev3 34 | 35 | ## Obj-C/Swift specific 36 | *.hmap 37 | 38 | ## App packaging 39 | *.ipa 40 | *.dSYM.zip 41 | *.dSYM 42 | 43 | ## Playgrounds 44 | timeline.xctimeline 45 | playground.xcworkspace 46 | 47 | # Swift Package Manager 48 | # 49 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 50 | # Packages/ 51 | # Package.pins 52 | # Package.resolved 53 | # *.xcodeproj 54 | # 55 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 56 | # hence it is not needed unless you have added a package configuration file to your project 57 | # .swiftpm 58 | 59 | .build/ 60 | 61 | # CocoaPods 62 | # 63 | # We recommend against adding the Pods directory to your .gitignore. However 64 | # you should judge for yourself, the pros and cons are mentioned at: 65 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 66 | 67 | Pods/ 68 | 69 | # Add this line if you want to avoid checking in source code from the Xcode workspace 70 | *.xcworkspace 71 | *.generated.swift 72 | 73 | # Carthage 74 | # 75 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 76 | # Carthage/Checkouts 77 | 78 | Carthage/Build/ 79 | 80 | # Accio dependency management 81 | Dependencies/ 82 | .accio/ 83 | 84 | # fastlane 85 | # 86 | # It is recommended to not store the screenshots in the git repo. 87 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 88 | # For more information about the recommended setup visit: 89 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 90 | 91 | fastlane/report.xml 92 | fastlane/Preview.html 93 | fastlane/screenshots/**/*.png 94 | fastlane/test_output 95 | 96 | # Code Injection 97 | # 98 | # After new code Injection tools there's a generated folder /iOSInjectionProject 99 | # https://github.com/johnno1962/injectionforxcode 100 | 101 | iOSInjectionProject/ -------------------------------------------------------------------------------- /Previews/EmptyGridView.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UIXaker/UIExamples/e482ff942f9e3a0fd4b3368c209bfa2be28141e5/Previews/EmptyGridView.gif -------------------------------------------------------------------------------- /Previews/GalleryAccess.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UIXaker/UIExamples/e482ff942f9e3a0fd4b3368c209bfa2be28141e5/Previews/GalleryAccess.jpg -------------------------------------------------------------------------------- /Previews/LoadingView.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UIXaker/UIExamples/e482ff942f9e3a0fd4b3368c209bfa2be28141e5/Previews/LoadingView.gif -------------------------------------------------------------------------------- /Previews/NiceButton.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UIXaker/UIExamples/e482ff942f9e3a0fd4b3368c209bfa2be28141e5/Previews/NiceButton.gif -------------------------------------------------------------------------------- /Previews/NotificationsPermission.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UIXaker/UIExamples/e482ff942f9e3a0fd4b3368c209bfa2be28141e5/Previews/NotificationsPermission.jpg -------------------------------------------------------------------------------- /Previews/NotificationsPermission2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UIXaker/UIExamples/e482ff942f9e3a0fd4b3368c209bfa2be28141e5/Previews/NotificationsPermission2.jpg -------------------------------------------------------------------------------- /Previews/NotificationsPermission3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UIXaker/UIExamples/e482ff942f9e3a0fd4b3368c209bfa2be28141e5/Previews/NotificationsPermission3.jpg -------------------------------------------------------------------------------- /Previews/Particle.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UIXaker/UIExamples/e482ff942f9e3a0fd4b3368c209bfa2be28141e5/Previews/Particle.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UI Examples 2 | 3 | In this repository, you'll find a collection of small, reusable code snippets that you can freely use and integrate into your own projects. These tiny UI treasures have been carefully crafted, saving you time and effort in your development journey. 4 | 5 | ## Nice Button 6 | 7 | iOS system-style button with scaling down, haptic feedback, and smoothly rounded corners that change in the pressed state. Haptic effect when pressed. 8 | 9 | - SwiftUI 10 | 11 | 12 | Nice Button 13 | 14 | 15 | ## Gallery Access Restricted 16 | 17 | User-friendly Apple-style screen that provides a step-by-step explanation of how to enable full access to the photo library after user has previously restricted it. The screen includes an 'Open Settings' button that guides users to the specific app's permission settings. Each step is accompanied by rich and fully localizable previews that replicate the appearance of the iOS Settings app. This ensures a pixel-perfect UI and system colors. 18 | 19 | There are some delightful details: a variable background blur overlay beneath the 'Open Settings' button, consistently positioned at the bottom, and background blur applied to the small circle button, in line with Apple's design principles found in its native apps. 20 | 21 | - SwiftUI 22 | - Dark mode support 🌒 23 | - iOS 16-17 different settings support 24 | - Customizable & localizable 🇺🇸 25 | 26 | 27 | Gallery Access Restricted View 28 | 29 | ## Ask for Notifications Permission 30 | 31 | Apple-style implementation of a permission request screen for notifications. A user-friendly list displaying all types of notifications you can receive, along with icons (SF Symbols) and descriptions. This screen is scrollable, so you can add as many elements as you need. Selecting 'Turn on Notifications' will open a system alert requesting these permissions. 32 | 33 | There is one delightful detail: a variable background blur overlay beneath the 'Turn on Notifications' button, consistently positioned at the bottom. 34 | 35 | - SwiftUI 36 | - Dark mode support 🌒 37 | - Customizable & localizable 🇺🇸 38 | 39 | Ask for Notifications Permission 40 | 41 | ## Ask for Notifications Permission with Examples 42 | 43 | Pixel-perfect replication of iOS notification banners to make fully localizable screen asking for permissions. Screen displays all available notification types that application would like to send (animatable!), just like in real life, with all available scenarios. Add as much notifications as you need. Icons and pictures are fully customizable. Selecting 'Turn on Notifications' will open a system alert requesting these permissions. 44 | 45 | - SwiftUI 46 | - Dark mode support 🌒 47 | - Customizable & localizable 🇺🇸 48 | - Animated ✨ 49 | 50 | Ask for Notifications Permission 51 | 52 | ## Ask for Notifications Permission v3 53 | 54 | Revised and combined screen of 2 previous versions for notifications permission request. It represents a short stack of example banners, made with precision to replicate native iOS design. You can place your custom content within any banner, so the text is fully localizable and user-friendly. Stack is interactive so users can tap on this and see full sized notifications. Supports banners of direct messages, banners with images, and simple text push notifications (even multi-line). 55 | 56 | - SwiftUI 57 | - Dark mode support 🌒 58 | - Customizable & localizable 🇺🇸 59 | - Interactive & animated ✨ 60 | 61 | Ask for Notifications Permission 62 | 63 | ## Animated Grid (Empty Blank View) 64 | 65 | One of several options how to make an empty state for any content screen better. The grid on background is animatable, each row highlights itself with random color. 66 | 67 | - SwiftUI 68 | - Dark mode support 🌒 69 | - Animated✨ 70 | 71 | Empty Grid View 72 | 73 | ## Starry Sky Loading View 74 | 75 | Stunning loading screen ideal for fetching or downloading thousands of items at a time. Drawing inspiration from the mesmerizing vastness of the universe, it features a vibrant, colorful, and animated background blur imitating northern lights. Added particles mimic a starry night sky. As it loads, witness the starry backdrop dynamically scale in, evoking the grandeur of the big bang or the iconic light speed travels from Star Wars movies. This piece of art is designed to captivate and entertain users during extended wait times. Perfect for those aiming to leave a lasting impression, and for moments when users need to keep your app open throughout intricate processes. 76 | 77 | - SwiftUI 78 | - Dark mode support 🌒 79 | - Animated ✨ 80 | 81 | Starry Sky Loading View 82 | 83 | ## Magic Button 84 | 85 | Button style inspired by Framer's "Start with AI". Visual spectacle with particles that glow and orbit the button, paired with an animated gradient background. Detail-oriented interaction with faster particle movement upon touch down. Ideal for AI features or for spotlighting magical actions in any app. 86 | 87 | - SwiftUI 88 | - Animated ✨ 89 | - iOS 17+ 90 | 91 | Magic Button 92 | -------------------------------------------------------------------------------- /UIExamples.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 2C2E515D2AB4C8C000C4DCAF /* EmptyGridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2E515C2AB4C8C000C4DCAF /* EmptyGridView.swift */; }; 11 | 2C2E51602AB4C8DD00C4DCAF /* GridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2E515F2AB4C8DD00C4DCAF /* GridView.swift */; }; 12 | 2C317CD72AA20ABD00C0A3B4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C317CD62AA20ABD00C0A3B4 /* AppDelegate.swift */; }; 13 | 2C317CD92AA20ABD00C0A3B4 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C317CD82AA20ABD00C0A3B4 /* SceneDelegate.swift */; }; 14 | 2C317CDB2AA20ABD00C0A3B4 /* ExamplesListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C317CDA2AA20ABD00C0A3B4 /* ExamplesListController.swift */; }; 15 | 2C317CE02AA20ABE00C0A3B4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2C317CDF2AA20ABE00C0A3B4 /* Assets.xcassets */; }; 16 | 2C317CE32AA20ABE00C0A3B4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2C317CE12AA20ABE00C0A3B4 /* LaunchScreen.storyboard */; }; 17 | 2C3AA70D2AA79DCC00E0A051 /* NotificationSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C3AA70C2AA79DCC00E0A051 /* NotificationSetupView.swift */; }; 18 | 2C3AA7102AA8B39200E0A051 /* NotificationSetupModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C3AA70F2AA8B39200E0A051 /* NotificationSetupModel.swift */; }; 19 | 2C3AA7142AA8B3C700E0A051 /* View+Builders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C3AA7132AA8B3C700E0A051 /* View+Builders.swift */; }; 20 | 2C3AA7172AA8B49200E0A051 /* ExampleListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C3AA7162AA8B49200E0A051 /* ExampleListModel.swift */; }; 21 | 2C3AA71A2AA8B5C100E0A051 /* NotificationFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C3AA7192AA8B5C100E0A051 /* NotificationFeature.swift */; }; 22 | 2C7D25922AA8CB4700AF0069 /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = 2C7D25912AA8CB4700AF0069 /* SnapKit */; }; 23 | 2C7D25942AA8CEE600AF0069 /* GalleryAccess.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 2C7D25932AA8CEE600AF0069 /* GalleryAccess.jpg */; }; 24 | 2C7D25982AAA20B500AF0069 /* NotificationSetupView2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7D25972AAA20B500AF0069 /* NotificationSetupView2.swift */; }; 25 | 2C7D259B2AAA24AA00AF0069 /* NotificationPushModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7D259A2AAA24AA00AF0069 /* NotificationPushModel.swift */; }; 26 | 2C7D259D2AAB411000AF0069 /* NotificationPushImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7D259C2AAB411000AF0069 /* NotificationPushImageView.swift */; }; 27 | 2C8532ED2AA4B4EC002F3C48 /* ExamplesListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8532EC2AA4B4EC002F3C48 /* ExamplesListView.swift */; }; 28 | 2C8532F32AA4BAFA002F3C48 /* NiceButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8532F22AA4BAFA002F3C48 /* NiceButtonStyle.swift */; }; 29 | 2C8532F52AA4DD97002F3C48 /* CircleSmallButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8532F42AA4DD97002F3C48 /* CircleSmallButton.swift */; }; 30 | 2C8532FA2AA4F094002F3C48 /* GalleryAccessView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8532F92AA4F094002F3C48 /* GalleryAccessView.swift */; }; 31 | 2C8532FE2AA4F170002F3C48 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 2C8533002AA4F170002F3C48 /* Localizable.strings */; }; 32 | 2C85CA212AA20E130007928D /* ExampleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C85CA202AA20E130007928D /* ExampleCell.swift */; }; 33 | 2C8AD1BF2ACF30B3008C5644 /* MagicStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8AD1BE2ACF30B3008C5644 /* MagicStyle.swift */; }; 34 | 2C8AD1C12ACF30B8008C5644 /* MagicView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8AD1C02ACF30B8008C5644 /* MagicView.swift */; }; 35 | 2C8AD1C42ACF34E6008C5644 /* ParticleStyleBounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8AD1C32ACF34E6008C5644 /* ParticleStyleBounce.swift */; }; 36 | 2C8AD1C72ACF351E008C5644 /* ButtonParticleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8AD1C62ACF351E008C5644 /* ButtonParticleView.swift */; }; 37 | 2C8AD1C92ACF3567008C5644 /* ParticleAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8AD1C82ACF3567008C5644 /* ParticleAnimator.swift */; }; 38 | 2C8AD1CC2ACF3599008C5644 /* ButtonParticle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8AD1CB2ACF3599008C5644 /* ButtonParticle.swift */; }; 39 | 2C8AD1CE2ACF35D1008C5644 /* ProgressConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8AD1CD2ACF35D1008C5644 /* ProgressConverter.swift */; }; 40 | 2C8AD1D02ACF35FE008C5644 /* CustomShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8AD1CF2ACF35FE008C5644 /* CustomShape.swift */; }; 41 | 2C92A74C2AA2342900E31C0E /* NiceButtonExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C92A74B2AA2342900E31C0E /* NiceButtonExample.swift */; }; 42 | 2C92A74F2AA249DC00E31C0E /* NiceButton.gif in Resources */ = {isa = PBXBuildFile; fileRef = 2C92A74E2AA249DC00E31C0E /* NiceButton.gif */; }; 43 | 2CAEAEB02AC09C3C0091FC9A /* LoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CAEAEAF2AC09C3C0091FC9A /* LoadingView.swift */; }; 44 | 2CAEAEB22AC09C9F0091FC9A /* StarrySkyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CAEAEB12AC09C9F0091FC9A /* StarrySkyView.swift */; }; 45 | 2CAEAEB52AC09F680091FC9A /* LoadingParticle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CAEAEB42AC09F680091FC9A /* LoadingParticle.swift */; }; 46 | 2CAEAEB72AC0A8210091FC9A /* GridCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CAEAEB62AC0A8210091FC9A /* GridCell.swift */; }; 47 | 2CAEAEBA2AC0A85F0091FC9A /* GridPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CAEAEB92AC0A85F0091FC9A /* GridPosition.swift */; }; 48 | 2CC9B12A2AAE0AA0007ED438 /* NotificationPushView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC9B1292AAE0AA0007ED438 /* NotificationPushView.swift */; }; 49 | 2CE9AB332AAB74D6006BE8BA /* NotificationSetupView3.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE9AB322AAB74D6006BE8BA /* NotificationSetupView3.swift */; }; 50 | 2CF12FED2AA8BF900041BBD7 /* SwiftUIExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CF12FEC2AA8BF900041BBD7 /* SwiftUIExample.swift */; }; 51 | 2CF12FEF2AA8C2BA0041BBD7 /* UIColor+Extentions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CF12FEE2AA8C2BA0041BBD7 /* UIColor+Extentions.swift */; }; 52 | /* End PBXBuildFile section */ 53 | 54 | /* Begin PBXFileReference section */ 55 | 2C2E515C2AB4C8C000C4DCAF /* EmptyGridView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyGridView.swift; sourceTree = ""; }; 56 | 2C2E515F2AB4C8DD00C4DCAF /* GridView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridView.swift; sourceTree = ""; }; 57 | 2C317CD32AA20ABD00C0A3B4 /* UIExamples.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = UIExamples.app; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | 2C317CD62AA20ABD00C0A3B4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 59 | 2C317CD82AA20ABD00C0A3B4 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 60 | 2C317CDA2AA20ABD00C0A3B4 /* ExamplesListController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExamplesListController.swift; sourceTree = ""; }; 61 | 2C317CDF2AA20ABE00C0A3B4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 62 | 2C317CE22AA20ABE00C0A3B4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 63 | 2C317CE42AA20ABE00C0A3B4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 64 | 2C3AA70C2AA79DCC00E0A051 /* NotificationSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSetupView.swift; sourceTree = ""; }; 65 | 2C3AA70F2AA8B39200E0A051 /* NotificationSetupModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSetupModel.swift; sourceTree = ""; }; 66 | 2C3AA7132AA8B3C700E0A051 /* View+Builders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Builders.swift"; sourceTree = ""; }; 67 | 2C3AA7162AA8B49200E0A051 /* ExampleListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleListModel.swift; sourceTree = ""; }; 68 | 2C3AA7192AA8B5C100E0A051 /* NotificationFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationFeature.swift; sourceTree = ""; }; 69 | 2C7D25932AA8CEE600AF0069 /* GalleryAccess.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = GalleryAccess.jpg; sourceTree = ""; }; 70 | 2C7D25972AAA20B500AF0069 /* NotificationSetupView2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSetupView2.swift; sourceTree = ""; }; 71 | 2C7D259A2AAA24AA00AF0069 /* NotificationPushModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPushModel.swift; sourceTree = ""; }; 72 | 2C7D259C2AAB411000AF0069 /* NotificationPushImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPushImageView.swift; sourceTree = ""; }; 73 | 2C7D25A02AAB416000AF0069 /* NotificationPushView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPushView.swift; sourceTree = ""; }; 74 | 2C8532EC2AA4B4EC002F3C48 /* ExamplesListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExamplesListView.swift; sourceTree = ""; }; 75 | 2C8532F22AA4BAFA002F3C48 /* NiceButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NiceButtonStyle.swift; sourceTree = ""; }; 76 | 2C8532F42AA4DD97002F3C48 /* CircleSmallButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleSmallButton.swift; sourceTree = ""; }; 77 | 2C8532F92AA4F094002F3C48 /* GalleryAccessView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryAccessView.swift; sourceTree = ""; }; 78 | 2C8532FF2AA4F170002F3C48 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 79 | 2C85CA202AA20E130007928D /* ExampleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleCell.swift; sourceTree = ""; }; 80 | 2C8AD1BE2ACF30B3008C5644 /* MagicStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MagicStyle.swift; sourceTree = ""; }; 81 | 2C8AD1C02ACF30B8008C5644 /* MagicView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MagicView.swift; sourceTree = ""; }; 82 | 2C8AD1C32ACF34E6008C5644 /* ParticleStyleBounce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticleStyleBounce.swift; sourceTree = ""; }; 83 | 2C8AD1C62ACF351E008C5644 /* ButtonParticleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonParticleView.swift; sourceTree = ""; }; 84 | 2C8AD1C82ACF3567008C5644 /* ParticleAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticleAnimator.swift; sourceTree = ""; }; 85 | 2C8AD1CB2ACF3599008C5644 /* ButtonParticle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonParticle.swift; sourceTree = ""; }; 86 | 2C8AD1CD2ACF35D1008C5644 /* ProgressConverter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressConverter.swift; sourceTree = ""; }; 87 | 2C8AD1CF2ACF35FE008C5644 /* CustomShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomShape.swift; sourceTree = ""; }; 88 | 2C92A74B2AA2342900E31C0E /* NiceButtonExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NiceButtonExample.swift; sourceTree = ""; }; 89 | 2C92A74E2AA249DC00E31C0E /* NiceButton.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = NiceButton.gif; sourceTree = ""; }; 90 | 2CAEAEAF2AC09C3C0091FC9A /* LoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingView.swift; sourceTree = ""; }; 91 | 2CAEAEB12AC09C9F0091FC9A /* StarrySkyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarrySkyView.swift; sourceTree = ""; }; 92 | 2CAEAEB42AC09F680091FC9A /* LoadingParticle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingParticle.swift; sourceTree = ""; }; 93 | 2CAEAEB62AC0A8210091FC9A /* GridCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridCell.swift; sourceTree = ""; }; 94 | 2CAEAEB92AC0A85F0091FC9A /* GridPosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridPosition.swift; sourceTree = ""; }; 95 | 2CC9B1292AAE0AA0007ED438 /* NotificationPushView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPushView.swift; sourceTree = ""; }; 96 | 2CE9AB322AAB74D6006BE8BA /* NotificationSetupView3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSetupView3.swift; sourceTree = ""; }; 97 | 2CF12FEC2AA8BF900041BBD7 /* SwiftUIExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIExample.swift; sourceTree = ""; }; 98 | 2CF12FEE2AA8C2BA0041BBD7 /* UIColor+Extentions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extentions.swift"; sourceTree = ""; }; 99 | /* End PBXFileReference section */ 100 | 101 | /* Begin PBXFrameworksBuildPhase section */ 102 | 2C317CD02AA20ABD00C0A3B4 /* Frameworks */ = { 103 | isa = PBXFrameworksBuildPhase; 104 | buildActionMask = 2147483647; 105 | files = ( 106 | 2C7D25922AA8CB4700AF0069 /* SnapKit in Frameworks */, 107 | ); 108 | runOnlyForDeploymentPostprocessing = 0; 109 | }; 110 | /* End PBXFrameworksBuildPhase section */ 111 | 112 | /* Begin PBXGroup section */ 113 | 2C2E51522AB4B6C600C4DCAF /* NiceButtonStyle */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | 2C8532F22AA4BAFA002F3C48 /* NiceButtonStyle.swift */, 117 | ); 118 | path = NiceButtonStyle; 119 | sourceTree = ""; 120 | }; 121 | 2C2E515A2AB4C8A300C4DCAF /* EmptyGrid */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | 2CAEAEB82AC0A84A0091FC9A /* Model */, 125 | 2C2E515B2AB4C8B100C4DCAF /* View */, 126 | ); 127 | path = EmptyGrid; 128 | sourceTree = ""; 129 | }; 130 | 2C2E515B2AB4C8B100C4DCAF /* View */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | 2CAEAEB62AC0A8210091FC9A /* GridCell.swift */, 134 | 2C2E515F2AB4C8DD00C4DCAF /* GridView.swift */, 135 | 2C2E515C2AB4C8C000C4DCAF /* EmptyGridView.swift */, 136 | ); 137 | path = View; 138 | sourceTree = ""; 139 | }; 140 | 2C317CCA2AA20ABD00C0A3B4 = { 141 | isa = PBXGroup; 142 | children = ( 143 | 2C92A74D2AA2495600E31C0E /* Previews */, 144 | 2C317CD52AA20ABD00C0A3B4 /* UIExamples */, 145 | 2C317CD42AA20ABD00C0A3B4 /* Products */, 146 | 2CC9B1262AAE09CA007ED438 /* Recovered References */, 147 | ); 148 | sourceTree = ""; 149 | }; 150 | 2C317CD42AA20ABD00C0A3B4 /* Products */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | 2C317CD32AA20ABD00C0A3B4 /* UIExamples.app */, 154 | ); 155 | name = Products; 156 | sourceTree = ""; 157 | }; 158 | 2C317CD52AA20ABD00C0A3B4 /* UIExamples */ = { 159 | isa = PBXGroup; 160 | children = ( 161 | 2C8532E62AA4B439002F3C48 /* App */, 162 | 2C8532E92AA4B475002F3C48 /* Modules */, 163 | 2C8532E72AA4B459002F3C48 /* Shared */, 164 | ); 165 | path = UIExamples; 166 | sourceTree = ""; 167 | }; 168 | 2C3AA70A2AA79DA600E0A051 /* Notifications */ = { 169 | isa = PBXGroup; 170 | children = ( 171 | 2C7D25992AAA20F400AF0069 /* Subview */, 172 | 2C3AA70E2AA8B38200E0A051 /* Model */, 173 | 2C3AA70B2AA79DB200E0A051 /* View1 */, 174 | 2C7D25962AAA209A00AF0069 /* View2 */, 175 | 2CE9AB312AAB74C7006BE8BA /* View3 */, 176 | ); 177 | path = Notifications; 178 | sourceTree = ""; 179 | }; 180 | 2C3AA70B2AA79DB200E0A051 /* View1 */ = { 181 | isa = PBXGroup; 182 | children = ( 183 | 2C3AA70C2AA79DCC00E0A051 /* NotificationSetupView.swift */, 184 | ); 185 | path = View1; 186 | sourceTree = ""; 187 | }; 188 | 2C3AA70E2AA8B38200E0A051 /* Model */ = { 189 | isa = PBXGroup; 190 | children = ( 191 | 2C3AA70F2AA8B39200E0A051 /* NotificationSetupModel.swift */, 192 | ); 193 | path = Model; 194 | sourceTree = ""; 195 | }; 196 | 2C3AA7122AA8B3B800E0A051 /* Extentions */ = { 197 | isa = PBXGroup; 198 | children = ( 199 | 2C3AA7132AA8B3C700E0A051 /* View+Builders.swift */, 200 | 2CF12FEE2AA8C2BA0041BBD7 /* UIColor+Extentions.swift */, 201 | ); 202 | path = Extentions; 203 | sourceTree = ""; 204 | }; 205 | 2C3AA7152AA8B48200E0A051 /* Model */ = { 206 | isa = PBXGroup; 207 | children = ( 208 | 2C3AA7162AA8B49200E0A051 /* ExampleListModel.swift */, 209 | ); 210 | path = Model; 211 | sourceTree = ""; 212 | }; 213 | 2C3AA7182AA8B5B100E0A051 /* Models */ = { 214 | isa = PBXGroup; 215 | children = ( 216 | 2C3AA7192AA8B5C100E0A051 /* NotificationFeature.swift */, 217 | 2CF12FEC2AA8BF900041BBD7 /* SwiftUIExample.swift */, 218 | 2C7D259A2AAA24AA00AF0069 /* NotificationPushModel.swift */, 219 | ); 220 | path = Models; 221 | sourceTree = ""; 222 | }; 223 | 2C7D25962AAA209A00AF0069 /* View2 */ = { 224 | isa = PBXGroup; 225 | children = ( 226 | 2C7D25972AAA20B500AF0069 /* NotificationSetupView2.swift */, 227 | ); 228 | path = View2; 229 | sourceTree = ""; 230 | }; 231 | 2C7D25992AAA20F400AF0069 /* Subview */ = { 232 | isa = PBXGroup; 233 | children = ( 234 | 2C7D259C2AAB411000AF0069 /* NotificationPushImageView.swift */, 235 | 2CC9B1292AAE0AA0007ED438 /* NotificationPushView.swift */, 236 | ); 237 | path = Subview; 238 | sourceTree = ""; 239 | }; 240 | 2C8532E62AA4B439002F3C48 /* App */ = { 241 | isa = PBXGroup; 242 | children = ( 243 | 2C317CD62AA20ABD00C0A3B4 /* AppDelegate.swift */, 244 | 2C317CD82AA20ABD00C0A3B4 /* SceneDelegate.swift */, 245 | 2C317CE12AA20ABE00C0A3B4 /* LaunchScreen.storyboard */, 246 | 2C317CE42AA20ABE00C0A3B4 /* Info.plist */, 247 | ); 248 | path = App; 249 | sourceTree = ""; 250 | }; 251 | 2C8532E72AA4B459002F3C48 /* Shared */ = { 252 | isa = PBXGroup; 253 | children = ( 254 | 2C3AA7182AA8B5B100E0A051 /* Models */, 255 | 2C3AA7122AA8B3B800E0A051 /* Extentions */, 256 | 2C8532F12AA4BADA002F3C48 /* Views */, 257 | 2C8532E82AA4B460002F3C48 /* Resources */, 258 | ); 259 | path = Shared; 260 | sourceTree = ""; 261 | }; 262 | 2C8532E82AA4B460002F3C48 /* Resources */ = { 263 | isa = PBXGroup; 264 | children = ( 265 | 2C8532FB2AA4F116002F3C48 /* Strings */, 266 | 2C317CDF2AA20ABE00C0A3B4 /* Assets.xcassets */, 267 | ); 268 | path = Resources; 269 | sourceTree = ""; 270 | }; 271 | 2C8532E92AA4B475002F3C48 /* Modules */ = { 272 | isa = PBXGroup; 273 | children = ( 274 | 2C8532EA2AA4B48C002F3C48 /* ExamplesList */, 275 | 2C8532EE2AA4BA53002F3C48 /* NiceButton */, 276 | 2C8532F72AA4F062002F3C48 /* GalleryAccess */, 277 | 2C3AA70A2AA79DA600E0A051 /* Notifications */, 278 | 2C2E515A2AB4C8A300C4DCAF /* EmptyGrid */, 279 | 2CAEAEAD2AC09AD20091FC9A /* Loading */, 280 | 2C8AD1BB2ACF3056008C5644 /* MagicButton */, 281 | ); 282 | path = Modules; 283 | sourceTree = ""; 284 | }; 285 | 2C8532EA2AA4B48C002F3C48 /* ExamplesList */ = { 286 | isa = PBXGroup; 287 | children = ( 288 | 2C3AA7152AA8B48200E0A051 /* Model */, 289 | 2C8532EF2AA4BA8F002F3C48 /* Cell */, 290 | 2C8532EB2AA4B499002F3C48 /* View */, 291 | 2C317CDA2AA20ABD00C0A3B4 /* ExamplesListController.swift */, 292 | ); 293 | path = ExamplesList; 294 | sourceTree = ""; 295 | }; 296 | 2C8532EB2AA4B499002F3C48 /* View */ = { 297 | isa = PBXGroup; 298 | children = ( 299 | 2C8532EC2AA4B4EC002F3C48 /* ExamplesListView.swift */, 300 | ); 301 | path = View; 302 | sourceTree = ""; 303 | }; 304 | 2C8532EE2AA4BA53002F3C48 /* NiceButton */ = { 305 | isa = PBXGroup; 306 | children = ( 307 | 2C2E51522AB4B6C600C4DCAF /* NiceButtonStyle */, 308 | 2C8532F02AA4BAA0002F3C48 /* View */, 309 | ); 310 | path = NiceButton; 311 | sourceTree = ""; 312 | }; 313 | 2C8532EF2AA4BA8F002F3C48 /* Cell */ = { 314 | isa = PBXGroup; 315 | children = ( 316 | 2C85CA202AA20E130007928D /* ExampleCell.swift */, 317 | ); 318 | path = Cell; 319 | sourceTree = ""; 320 | }; 321 | 2C8532F02AA4BAA0002F3C48 /* View */ = { 322 | isa = PBXGroup; 323 | children = ( 324 | 2C92A74B2AA2342900E31C0E /* NiceButtonExample.swift */, 325 | ); 326 | path = View; 327 | sourceTree = ""; 328 | }; 329 | 2C8532F12AA4BADA002F3C48 /* Views */ = { 330 | isa = PBXGroup; 331 | children = ( 332 | 2C8532F42AA4DD97002F3C48 /* CircleSmallButton.swift */, 333 | ); 334 | path = Views; 335 | sourceTree = ""; 336 | }; 337 | 2C8532F72AA4F062002F3C48 /* GalleryAccess */ = { 338 | isa = PBXGroup; 339 | children = ( 340 | 2C8532F82AA4F08A002F3C48 /* View */, 341 | ); 342 | path = GalleryAccess; 343 | sourceTree = ""; 344 | }; 345 | 2C8532F82AA4F08A002F3C48 /* View */ = { 346 | isa = PBXGroup; 347 | children = ( 348 | 2C8532F92AA4F094002F3C48 /* GalleryAccessView.swift */, 349 | ); 350 | path = View; 351 | sourceTree = ""; 352 | }; 353 | 2C8532FB2AA4F116002F3C48 /* Strings */ = { 354 | isa = PBXGroup; 355 | children = ( 356 | 2C8533002AA4F170002F3C48 /* Localizable.strings */, 357 | ); 358 | path = Strings; 359 | sourceTree = ""; 360 | }; 361 | 2C8AD1BB2ACF3056008C5644 /* MagicButton */ = { 362 | isa = PBXGroup; 363 | children = ( 364 | 2C8AD1CA2ACF357D008C5644 /* Model */, 365 | 2C8AD1C22ACF34D6008C5644 /* Shared */, 366 | 2C8AD1BD2ACF3079008C5644 /* Style */, 367 | 2C8AD1C52ACF3500008C5644 /* Subview */, 368 | 2C8AD1BC2ACF3069008C5644 /* View */, 369 | ); 370 | path = MagicButton; 371 | sourceTree = ""; 372 | }; 373 | 2C8AD1BC2ACF3069008C5644 /* View */ = { 374 | isa = PBXGroup; 375 | children = ( 376 | 2C8AD1C02ACF30B8008C5644 /* MagicView.swift */, 377 | ); 378 | path = View; 379 | sourceTree = ""; 380 | }; 381 | 2C8AD1BD2ACF3079008C5644 /* Style */ = { 382 | isa = PBXGroup; 383 | children = ( 384 | 2C8AD1BE2ACF30B3008C5644 /* MagicStyle.swift */, 385 | ); 386 | path = Style; 387 | sourceTree = ""; 388 | }; 389 | 2C8AD1C22ACF34D6008C5644 /* Shared */ = { 390 | isa = PBXGroup; 391 | children = ( 392 | 2C8AD1C32ACF34E6008C5644 /* ParticleStyleBounce.swift */, 393 | 2C8AD1C82ACF3567008C5644 /* ParticleAnimator.swift */, 394 | 2C8AD1CD2ACF35D1008C5644 /* ProgressConverter.swift */, 395 | ); 396 | path = Shared; 397 | sourceTree = ""; 398 | }; 399 | 2C8AD1C52ACF3500008C5644 /* Subview */ = { 400 | isa = PBXGroup; 401 | children = ( 402 | 2C8AD1C62ACF351E008C5644 /* ButtonParticleView.swift */, 403 | 2C8AD1CF2ACF35FE008C5644 /* CustomShape.swift */, 404 | ); 405 | path = Subview; 406 | sourceTree = ""; 407 | }; 408 | 2C8AD1CA2ACF357D008C5644 /* Model */ = { 409 | isa = PBXGroup; 410 | children = ( 411 | 2C8AD1CB2ACF3599008C5644 /* ButtonParticle.swift */, 412 | ); 413 | path = Model; 414 | sourceTree = ""; 415 | }; 416 | 2C92A74D2AA2495600E31C0E /* Previews */ = { 417 | isa = PBXGroup; 418 | children = ( 419 | 2C92A74E2AA249DC00E31C0E /* NiceButton.gif */, 420 | 2C7D25932AA8CEE600AF0069 /* GalleryAccess.jpg */, 421 | ); 422 | path = Previews; 423 | sourceTree = ""; 424 | }; 425 | 2CAEAEAD2AC09AD20091FC9A /* Loading */ = { 426 | isa = PBXGroup; 427 | children = ( 428 | 2CAEAEB32AC09F500091FC9A /* Model */, 429 | 2CAEAEAE2AC09B100091FC9A /* View */, 430 | ); 431 | path = Loading; 432 | sourceTree = ""; 433 | }; 434 | 2CAEAEAE2AC09B100091FC9A /* View */ = { 435 | isa = PBXGroup; 436 | children = ( 437 | 2CAEAEB12AC09C9F0091FC9A /* StarrySkyView.swift */, 438 | 2CAEAEAF2AC09C3C0091FC9A /* LoadingView.swift */, 439 | ); 440 | path = View; 441 | sourceTree = ""; 442 | }; 443 | 2CAEAEB32AC09F500091FC9A /* Model */ = { 444 | isa = PBXGroup; 445 | children = ( 446 | 2CAEAEB42AC09F680091FC9A /* LoadingParticle.swift */, 447 | ); 448 | path = Model; 449 | sourceTree = ""; 450 | }; 451 | 2CAEAEB82AC0A84A0091FC9A /* Model */ = { 452 | isa = PBXGroup; 453 | children = ( 454 | 2CAEAEB92AC0A85F0091FC9A /* GridPosition.swift */, 455 | ); 456 | path = Model; 457 | sourceTree = ""; 458 | }; 459 | 2CC9B1262AAE09CA007ED438 /* Recovered References */ = { 460 | isa = PBXGroup; 461 | children = ( 462 | 2C7D25A02AAB416000AF0069 /* NotificationPushView.swift */, 463 | ); 464 | name = "Recovered References"; 465 | sourceTree = ""; 466 | }; 467 | 2CE9AB312AAB74C7006BE8BA /* View3 */ = { 468 | isa = PBXGroup; 469 | children = ( 470 | 2CE9AB322AAB74D6006BE8BA /* NotificationSetupView3.swift */, 471 | ); 472 | path = View3; 473 | sourceTree = ""; 474 | }; 475 | /* End PBXGroup section */ 476 | 477 | /* Begin PBXNativeTarget section */ 478 | 2C317CD22AA20ABD00C0A3B4 /* UIExamples */ = { 479 | isa = PBXNativeTarget; 480 | buildConfigurationList = 2C317CE72AA20ABE00C0A3B4 /* Build configuration list for PBXNativeTarget "UIExamples" */; 481 | buildPhases = ( 482 | 2C317CCF2AA20ABD00C0A3B4 /* Sources */, 483 | 2C317CD02AA20ABD00C0A3B4 /* Frameworks */, 484 | 2C317CD12AA20ABD00C0A3B4 /* Resources */, 485 | ); 486 | buildRules = ( 487 | ); 488 | dependencies = ( 489 | ); 490 | name = UIExamples; 491 | packageProductDependencies = ( 492 | 2C7D25912AA8CB4700AF0069 /* SnapKit */, 493 | ); 494 | productName = UIExamples; 495 | productReference = 2C317CD32AA20ABD00C0A3B4 /* UIExamples.app */; 496 | productType = "com.apple.product-type.application"; 497 | }; 498 | /* End PBXNativeTarget section */ 499 | 500 | /* Begin PBXProject section */ 501 | 2C317CCB2AA20ABD00C0A3B4 /* Project object */ = { 502 | isa = PBXProject; 503 | attributes = { 504 | BuildIndependentTargetsInParallel = 1; 505 | LastSwiftUpdateCheck = 1430; 506 | LastUpgradeCheck = 1430; 507 | TargetAttributes = { 508 | 2C317CD22AA20ABD00C0A3B4 = { 509 | CreatedOnToolsVersion = 14.3.1; 510 | }; 511 | }; 512 | }; 513 | buildConfigurationList = 2C317CCE2AA20ABD00C0A3B4 /* Build configuration list for PBXProject "UIExamples" */; 514 | compatibilityVersion = "Xcode 14.0"; 515 | developmentRegion = en; 516 | hasScannedForEncodings = 0; 517 | knownRegions = ( 518 | en, 519 | Base, 520 | ); 521 | mainGroup = 2C317CCA2AA20ABD00C0A3B4; 522 | packageReferences = ( 523 | 2C7D25902AA8CB4700AF0069 /* XCRemoteSwiftPackageReference "SnapKit" */, 524 | ); 525 | productRefGroup = 2C317CD42AA20ABD00C0A3B4 /* Products */; 526 | projectDirPath = ""; 527 | projectRoot = ""; 528 | targets = ( 529 | 2C317CD22AA20ABD00C0A3B4 /* UIExamples */, 530 | ); 531 | }; 532 | /* End PBXProject section */ 533 | 534 | /* Begin PBXResourcesBuildPhase section */ 535 | 2C317CD12AA20ABD00C0A3B4 /* Resources */ = { 536 | isa = PBXResourcesBuildPhase; 537 | buildActionMask = 2147483647; 538 | files = ( 539 | 2C317CE32AA20ABE00C0A3B4 /* LaunchScreen.storyboard in Resources */, 540 | 2C8532FE2AA4F170002F3C48 /* Localizable.strings in Resources */, 541 | 2C7D25942AA8CEE600AF0069 /* GalleryAccess.jpg in Resources */, 542 | 2C317CE02AA20ABE00C0A3B4 /* Assets.xcassets in Resources */, 543 | 2C92A74F2AA249DC00E31C0E /* NiceButton.gif in Resources */, 544 | ); 545 | runOnlyForDeploymentPostprocessing = 0; 546 | }; 547 | /* End PBXResourcesBuildPhase section */ 548 | 549 | /* Begin PBXSourcesBuildPhase section */ 550 | 2C317CCF2AA20ABD00C0A3B4 /* Sources */ = { 551 | isa = PBXSourcesBuildPhase; 552 | buildActionMask = 2147483647; 553 | files = ( 554 | 2C85CA212AA20E130007928D /* ExampleCell.swift in Sources */, 555 | 2CC9B12A2AAE0AA0007ED438 /* NotificationPushView.swift in Sources */, 556 | 2C92A74C2AA2342900E31C0E /* NiceButtonExample.swift in Sources */, 557 | 2C8532F32AA4BAFA002F3C48 /* NiceButtonStyle.swift in Sources */, 558 | 2C3AA70D2AA79DCC00E0A051 /* NotificationSetupView.swift in Sources */, 559 | 2C8AD1BF2ACF30B3008C5644 /* MagicStyle.swift in Sources */, 560 | 2CAEAEB02AC09C3C0091FC9A /* LoadingView.swift in Sources */, 561 | 2C8AD1C92ACF3567008C5644 /* ParticleAnimator.swift in Sources */, 562 | 2CAEAEB52AC09F680091FC9A /* LoadingParticle.swift in Sources */, 563 | 2C8532FA2AA4F094002F3C48 /* GalleryAccessView.swift in Sources */, 564 | 2C8AD1C12ACF30B8008C5644 /* MagicView.swift in Sources */, 565 | 2CAEAEBA2AC0A85F0091FC9A /* GridPosition.swift in Sources */, 566 | 2C2E515D2AB4C8C000C4DCAF /* EmptyGridView.swift in Sources */, 567 | 2C317CDB2AA20ABD00C0A3B4 /* ExamplesListController.swift in Sources */, 568 | 2C8532ED2AA4B4EC002F3C48 /* ExamplesListView.swift in Sources */, 569 | 2CAEAEB22AC09C9F0091FC9A /* StarrySkyView.swift in Sources */, 570 | 2C3AA7172AA8B49200E0A051 /* ExampleListModel.swift in Sources */, 571 | 2C8AD1CE2ACF35D1008C5644 /* ProgressConverter.swift in Sources */, 572 | 2C2E51602AB4C8DD00C4DCAF /* GridView.swift in Sources */, 573 | 2CAEAEB72AC0A8210091FC9A /* GridCell.swift in Sources */, 574 | 2C8AD1D02ACF35FE008C5644 /* CustomShape.swift in Sources */, 575 | 2C8532F52AA4DD97002F3C48 /* CircleSmallButton.swift in Sources */, 576 | 2C317CD72AA20ABD00C0A3B4 /* AppDelegate.swift in Sources */, 577 | 2C3AA7142AA8B3C700E0A051 /* View+Builders.swift in Sources */, 578 | 2C7D25982AAA20B500AF0069 /* NotificationSetupView2.swift in Sources */, 579 | 2C7D259B2AAA24AA00AF0069 /* NotificationPushModel.swift in Sources */, 580 | 2C317CD92AA20ABD00C0A3B4 /* SceneDelegate.swift in Sources */, 581 | 2CE9AB332AAB74D6006BE8BA /* NotificationSetupView3.swift in Sources */, 582 | 2C8AD1C42ACF34E6008C5644 /* ParticleStyleBounce.swift in Sources */, 583 | 2CF12FEF2AA8C2BA0041BBD7 /* UIColor+Extentions.swift in Sources */, 584 | 2C7D259D2AAB411000AF0069 /* NotificationPushImageView.swift in Sources */, 585 | 2C3AA7102AA8B39200E0A051 /* NotificationSetupModel.swift in Sources */, 586 | 2CF12FED2AA8BF900041BBD7 /* SwiftUIExample.swift in Sources */, 587 | 2C8AD1C72ACF351E008C5644 /* ButtonParticleView.swift in Sources */, 588 | 2C3AA71A2AA8B5C100E0A051 /* NotificationFeature.swift in Sources */, 589 | 2C8AD1CC2ACF3599008C5644 /* ButtonParticle.swift in Sources */, 590 | ); 591 | runOnlyForDeploymentPostprocessing = 0; 592 | }; 593 | /* End PBXSourcesBuildPhase section */ 594 | 595 | /* Begin PBXVariantGroup section */ 596 | 2C317CE12AA20ABE00C0A3B4 /* LaunchScreen.storyboard */ = { 597 | isa = PBXVariantGroup; 598 | children = ( 599 | 2C317CE22AA20ABE00C0A3B4 /* Base */, 600 | ); 601 | name = LaunchScreen.storyboard; 602 | sourceTree = ""; 603 | }; 604 | 2C8533002AA4F170002F3C48 /* Localizable.strings */ = { 605 | isa = PBXVariantGroup; 606 | children = ( 607 | 2C8532FF2AA4F170002F3C48 /* en */, 608 | ); 609 | name = Localizable.strings; 610 | sourceTree = ""; 611 | }; 612 | /* End PBXVariantGroup section */ 613 | 614 | /* Begin XCBuildConfiguration section */ 615 | 2C317CE52AA20ABE00C0A3B4 /* Debug */ = { 616 | isa = XCBuildConfiguration; 617 | buildSettings = { 618 | ALWAYS_SEARCH_USER_PATHS = NO; 619 | CLANG_ANALYZER_NONNULL = YES; 620 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 621 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 622 | CLANG_ENABLE_MODULES = YES; 623 | CLANG_ENABLE_OBJC_ARC = YES; 624 | CLANG_ENABLE_OBJC_WEAK = YES; 625 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 626 | CLANG_WARN_BOOL_CONVERSION = YES; 627 | CLANG_WARN_COMMA = YES; 628 | CLANG_WARN_CONSTANT_CONVERSION = YES; 629 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 630 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 631 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 632 | CLANG_WARN_EMPTY_BODY = YES; 633 | CLANG_WARN_ENUM_CONVERSION = YES; 634 | CLANG_WARN_INFINITE_RECURSION = YES; 635 | CLANG_WARN_INT_CONVERSION = YES; 636 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 637 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 638 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 639 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 640 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 641 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 642 | CLANG_WARN_STRICT_PROTOTYPES = YES; 643 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 644 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 645 | CLANG_WARN_UNREACHABLE_CODE = YES; 646 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 647 | COPY_PHASE_STRIP = NO; 648 | DEBUG_INFORMATION_FORMAT = dwarf; 649 | ENABLE_STRICT_OBJC_MSGSEND = YES; 650 | ENABLE_TESTABILITY = YES; 651 | GCC_C_LANGUAGE_STANDARD = gnu11; 652 | GCC_DYNAMIC_NO_PIC = NO; 653 | GCC_NO_COMMON_BLOCKS = YES; 654 | GCC_OPTIMIZATION_LEVEL = 0; 655 | GCC_PREPROCESSOR_DEFINITIONS = ( 656 | "DEBUG=1", 657 | "$(inherited)", 658 | ); 659 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 660 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 661 | GCC_WARN_UNDECLARED_SELECTOR = YES; 662 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 663 | GCC_WARN_UNUSED_FUNCTION = YES; 664 | GCC_WARN_UNUSED_VARIABLE = YES; 665 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 666 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 667 | MTL_FAST_MATH = YES; 668 | ONLY_ACTIVE_ARCH = YES; 669 | SDKROOT = iphoneos; 670 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 671 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 672 | }; 673 | name = Debug; 674 | }; 675 | 2C317CE62AA20ABE00C0A3B4 /* Release */ = { 676 | isa = XCBuildConfiguration; 677 | buildSettings = { 678 | ALWAYS_SEARCH_USER_PATHS = NO; 679 | CLANG_ANALYZER_NONNULL = YES; 680 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 681 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 682 | CLANG_ENABLE_MODULES = YES; 683 | CLANG_ENABLE_OBJC_ARC = YES; 684 | CLANG_ENABLE_OBJC_WEAK = YES; 685 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 686 | CLANG_WARN_BOOL_CONVERSION = YES; 687 | CLANG_WARN_COMMA = YES; 688 | CLANG_WARN_CONSTANT_CONVERSION = YES; 689 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 690 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 691 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 692 | CLANG_WARN_EMPTY_BODY = YES; 693 | CLANG_WARN_ENUM_CONVERSION = YES; 694 | CLANG_WARN_INFINITE_RECURSION = YES; 695 | CLANG_WARN_INT_CONVERSION = YES; 696 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 697 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 698 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 699 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 700 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 701 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 702 | CLANG_WARN_STRICT_PROTOTYPES = YES; 703 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 704 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 705 | CLANG_WARN_UNREACHABLE_CODE = YES; 706 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 707 | COPY_PHASE_STRIP = NO; 708 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 709 | ENABLE_NS_ASSERTIONS = NO; 710 | ENABLE_STRICT_OBJC_MSGSEND = YES; 711 | GCC_C_LANGUAGE_STANDARD = gnu11; 712 | GCC_NO_COMMON_BLOCKS = YES; 713 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 714 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 715 | GCC_WARN_UNDECLARED_SELECTOR = YES; 716 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 717 | GCC_WARN_UNUSED_FUNCTION = YES; 718 | GCC_WARN_UNUSED_VARIABLE = YES; 719 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 720 | MTL_ENABLE_DEBUG_INFO = NO; 721 | MTL_FAST_MATH = YES; 722 | SDKROOT = iphoneos; 723 | SWIFT_COMPILATION_MODE = wholemodule; 724 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 725 | VALIDATE_PRODUCT = YES; 726 | }; 727 | name = Release; 728 | }; 729 | 2C317CE82AA20ABE00C0A3B4 /* Debug */ = { 730 | isa = XCBuildConfiguration; 731 | buildSettings = { 732 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 733 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 734 | CODE_SIGN_STYLE = Automatic; 735 | CURRENT_PROJECT_VERSION = 1; 736 | DEVELOPMENT_TEAM = N422RJM586; 737 | GENERATE_INFOPLIST_FILE = YES; 738 | INFOPLIST_FILE = UIExamples/App/Info.plist; 739 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 740 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 741 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 742 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 743 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 744 | LD_RUNPATH_SEARCH_PATHS = ( 745 | "$(inherited)", 746 | "@executable_path/Frameworks", 747 | ); 748 | MARKETING_VERSION = 1.0; 749 | PRODUCT_BUNDLE_IDENTIFIER = com.xaker.UIExamples; 750 | PRODUCT_NAME = "$(TARGET_NAME)"; 751 | SWIFT_EMIT_LOC_STRINGS = YES; 752 | SWIFT_VERSION = 5.0; 753 | TARGETED_DEVICE_FAMILY = "1,2"; 754 | }; 755 | name = Debug; 756 | }; 757 | 2C317CE92AA20ABE00C0A3B4 /* Release */ = { 758 | isa = XCBuildConfiguration; 759 | buildSettings = { 760 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 761 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 762 | CODE_SIGN_STYLE = Automatic; 763 | CURRENT_PROJECT_VERSION = 1; 764 | DEVELOPMENT_TEAM = N422RJM586; 765 | GENERATE_INFOPLIST_FILE = YES; 766 | INFOPLIST_FILE = UIExamples/App/Info.plist; 767 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 768 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 769 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 770 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 771 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 772 | LD_RUNPATH_SEARCH_PATHS = ( 773 | "$(inherited)", 774 | "@executable_path/Frameworks", 775 | ); 776 | MARKETING_VERSION = 1.0; 777 | PRODUCT_BUNDLE_IDENTIFIER = com.xaker.UIExamples; 778 | PRODUCT_NAME = "$(TARGET_NAME)"; 779 | SWIFT_EMIT_LOC_STRINGS = YES; 780 | SWIFT_VERSION = 5.0; 781 | TARGETED_DEVICE_FAMILY = "1,2"; 782 | }; 783 | name = Release; 784 | }; 785 | /* End XCBuildConfiguration section */ 786 | 787 | /* Begin XCConfigurationList section */ 788 | 2C317CCE2AA20ABD00C0A3B4 /* Build configuration list for PBXProject "UIExamples" */ = { 789 | isa = XCConfigurationList; 790 | buildConfigurations = ( 791 | 2C317CE52AA20ABE00C0A3B4 /* Debug */, 792 | 2C317CE62AA20ABE00C0A3B4 /* Release */, 793 | ); 794 | defaultConfigurationIsVisible = 0; 795 | defaultConfigurationName = Release; 796 | }; 797 | 2C317CE72AA20ABE00C0A3B4 /* Build configuration list for PBXNativeTarget "UIExamples" */ = { 798 | isa = XCConfigurationList; 799 | buildConfigurations = ( 800 | 2C317CE82AA20ABE00C0A3B4 /* Debug */, 801 | 2C317CE92AA20ABE00C0A3B4 /* Release */, 802 | ); 803 | defaultConfigurationIsVisible = 0; 804 | defaultConfigurationName = Release; 805 | }; 806 | /* End XCConfigurationList section */ 807 | 808 | /* Begin XCRemoteSwiftPackageReference section */ 809 | 2C7D25902AA8CB4700AF0069 /* XCRemoteSwiftPackageReference "SnapKit" */ = { 810 | isa = XCRemoteSwiftPackageReference; 811 | repositoryURL = "https://github.com/SnapKit/SnapKit.git"; 812 | requirement = { 813 | kind = upToNextMajorVersion; 814 | minimumVersion = 5.0.0; 815 | }; 816 | }; 817 | /* End XCRemoteSwiftPackageReference section */ 818 | 819 | /* Begin XCSwiftPackageProductDependency section */ 820 | 2C7D25912AA8CB4700AF0069 /* SnapKit */ = { 821 | isa = XCSwiftPackageProductDependency; 822 | package = 2C7D25902AA8CB4700AF0069 /* XCRemoteSwiftPackageReference "SnapKit" */; 823 | productName = SnapKit; 824 | }; 825 | /* End XCSwiftPackageProductDependency section */ 826 | }; 827 | rootObject = 2C317CCB2AA20ABD00C0A3B4 /* Project object */; 828 | } 829 | -------------------------------------------------------------------------------- /UIExamples/App/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // UIExamples 4 | // 5 | // Created by Max Xaker on 01.09.2023. 6 | // 7 | 8 | import UIKit 9 | 10 | 11 | @main 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 24 | // Called when a new scene session is being created. 25 | // Use this method to select a configuration to create the new scene with. 26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 27 | } 28 | 29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 30 | // Called when the user discards a scene session. 31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /UIExamples/App/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /UIExamples/App/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | UISceneConfigurations 10 | 11 | UIWindowSceneSessionRoleApplication 12 | 13 | 14 | UISceneConfigurationName 15 | Default Configuration 16 | UISceneDelegateClassName 17 | $(PRODUCT_MODULE_NAME).SceneDelegate 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /UIExamples/App/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 4 | 5 | var window: UIWindow? 6 | 7 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 8 | guard let windowScene = (scene as? UIWindowScene) else { return } 9 | 10 | window = UIWindow(windowScene: windowScene) 11 | window?.rootViewController = UINavigationController(rootViewController: ExamplesListController()) 12 | window?.makeKeyAndVisible() 13 | } 14 | 15 | func sceneDidDisconnect(_ scene: UIScene) { 16 | // Called as the scene is being released by the system. 17 | // This occurs shortly after the scene enters the background, or when its session is discarded. 18 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 19 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 20 | } 21 | 22 | func sceneDidBecomeActive(_ scene: UIScene) { 23 | // Called when the scene has moved from an inactive state to an active state. 24 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 25 | } 26 | 27 | func sceneWillResignActive(_ scene: UIScene) { 28 | // Called when the scene will move from an active state to an inactive state. 29 | // This may occur due to temporary interruptions (ex. an incoming phone call). 30 | } 31 | 32 | func sceneWillEnterForeground(_ scene: UIScene) { 33 | // Called as the scene transitions from the background to the foreground. 34 | // Use this method to undo the changes made on entering the background. 35 | } 36 | 37 | func sceneDidEnterBackground(_ scene: UIScene) { 38 | // Called as the scene transitions from the foreground to the background. 39 | // Use this method to save data, release shared resources, and store enough scene-specific state information 40 | // to restore the scene back to its current state. 41 | } 42 | 43 | 44 | } 45 | 46 | -------------------------------------------------------------------------------- /UIExamples/Modules/EmptyGrid/Model/GridPosition.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct GridPosition: Hashable { 4 | let row: Int 5 | let column: Int 6 | } 7 | -------------------------------------------------------------------------------- /UIExamples/Modules/EmptyGrid/View/EmptyGridView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct EmptyGridView: View { 4 | @Environment(\.dismiss) private var dismiss 5 | private let impactFeedback = UIImpactFeedbackGenerator(style: .soft) 6 | 7 | var body: some View { 8 | GeometryReader { geometry in 9 | ZStack(alignment: .topLeading) { 10 | GridView() 11 | .frame(width: geometry.size.width, height: geometry.size.height) 12 | 13 | VStack { 14 | Spacer() 15 | 16 | Text("You don't have any projects yet") 17 | .fixedSize(horizontal: false, vertical: true) 18 | .font(.largeTitle) 19 | .bold() 20 | .padding(.horizontal, 40) 21 | .multilineTextAlignment(.center) 22 | 23 | Spacer() 24 | } 25 | 26 | Button { 27 | impactFeedback.impactOccurred() 28 | dismiss() 29 | } label: { 30 | 31 | } 32 | .buttonStyle(CircleSmallButton(icon: "xmark")) 33 | } 34 | } 35 | } 36 | } 37 | 38 | #Preview { 39 | EmptyGridView() 40 | } 41 | -------------------------------------------------------------------------------- /UIExamples/Modules/EmptyGrid/View/GridCell.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct GridCell: View { 4 | let isGlowing: Bool 5 | let cellSize: CGFloat 6 | let glowingColorOpacityRange: ClosedRange 7 | let colors: Set 8 | 9 | var body: some View { 10 | RoundedRectangle(cornerRadius: 0) 11 | .stroke(Color.primary.opacity(0.13), lineWidth: 1) 12 | .frame(width: cellSize, height: cellSize) 13 | .background( 14 | RoundedRectangle(cornerRadius: 0) 15 | .fill(isGlowing ? colors.randomElement()!.opacity(randomGlowingColorOpacity()) : Color.clear) 16 | .opacity(isGlowing ? 0 : 1) 17 | .animation(Animation.linear(duration: 2).delay(isGlowing ? 0.3 : 0), value: isGlowing) 18 | ) 19 | } 20 | 21 | func randomGlowingColorOpacity() -> Double { 22 | Double.random(in: glowingColorOpacityRange) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /UIExamples/Modules/EmptyGrid/View/GridView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct GridView: View { 4 | @Environment(\.colorScheme) var colorScheme 5 | @State private var glowingCells: Set = [] 6 | @State private var currentCellPosition: GridPosition? 7 | 8 | private let cellSize: CGFloat = 96 9 | private let gridSize: Int = 10 10 | private let setColors: Set = [.yellow, .blue, .pink, .brown, .red, .green, .orange, .cyan, .mint, .indigo] 11 | var glowingColorOpacityRange: ClosedRange { 12 | return colorScheme == .dark ? 0.7...1 : 0.15...0.4 13 | } 14 | 15 | var body: some View { 16 | ZStack { 17 | VStack(spacing: 0) { 18 | ForEach(0.. some View { 42 | let borderWidth = CGFloat(110.0) 43 | let offset = CGFloat(20.0) 44 | let width = UIScreen.main.bounds.width - offset 45 | let height = UIScreen.main.bounds.height - offset 46 | let rect = CGRect( 47 | x: width / 2.0 + 50.0 + offset, 48 | y: 1.3 + (offset / 2.0), 49 | width: width, 50 | height: height 51 | ) 52 | 53 | Path(roundedRect: rect, cornerRadius: 80.0) 54 | .stroke(.background, style: .init(lineWidth: borderWidth)) 55 | .blur(radius: 70.0) 56 | } 57 | 58 | func startGlowAnimation() { 59 | currentCellPosition = generateRandomGridPosition(excluding: currentCellPosition) 60 | 61 | var randomPositions = generateRandomGridPositions(maxCount: 7, excluding: currentCellPosition) 62 | 63 | let animation = Animation.easeInOut(duration: 2).repeatForever() 64 | 65 | Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in 66 | withAnimation(animation) { 67 | randomPositions.removeAll(where: { glowingCells.contains($0) }) 68 | glowingCells = Set(randomPositions) 69 | } 70 | 71 | if randomPositions.isEmpty { 72 | timer.invalidate() 73 | startGlowAnimation() 74 | } 75 | } 76 | } 77 | 78 | func generateRandomGridPositions(maxCount: Int, excluding position: GridPosition?) -> [GridPosition] { 79 | var positions: [GridPosition] = [] 80 | 81 | while positions.count < maxCount { 82 | let randomPosition = generateRandomGridPosition(excluding: position) 83 | 84 | if !positions.contains(randomPosition) && !isNeighborPosition(randomPosition, of: position) { 85 | positions.append(randomPosition) 86 | } 87 | } 88 | 89 | return positions 90 | } 91 | 92 | func generateRandomGridPosition(excluding position: GridPosition?) -> GridPosition { 93 | let maxRow = gridSize - 1 94 | let maxColumn = gridSize - 1 95 | 96 | var randomPosition: GridPosition 97 | 98 | repeat { 99 | let row = Int.random(in: 0...maxRow) 100 | let column = Int.random(in: 0...maxColumn) 101 | randomPosition = GridPosition(row: row, column: column) 102 | } while randomPosition == position 103 | 104 | return randomPosition 105 | } 106 | 107 | func isNeighborPosition(_ position: GridPosition, of centerPosition: GridPosition?) -> Bool { 108 | guard let center = centerPosition else { 109 | return false 110 | } 111 | 112 | let rowDifference = abs(position.row - center.row) 113 | let columnDifference = abs(position.column - center.column) 114 | 115 | return (rowDifference == 0 && columnDifference == 1) || (rowDifference == 1 && columnDifference == 0) 116 | } 117 | } 118 | 119 | #Preview { 120 | GridView() 121 | } 122 | -------------------------------------------------------------------------------- /UIExamples/Modules/ExamplesList/Cell/ExampleCell.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class ExampleCell: UITableViewCell { 4 | 5 | let containerView: UIView = { 6 | let view = UIView() 7 | view.layer.cornerRadius = 20 8 | view.layer.cornerCurve = .continuous 9 | view.clipsToBounds = true 10 | return view 11 | }() 12 | 13 | let titleLabel: UILabel = { 14 | let label = UILabel() 15 | label.text = "Example" 16 | label.textColor = .white 17 | label.font = .systemFont(ofSize: 23, weight: .semibold) 18 | label.numberOfLines = 0 19 | 20 | return label 21 | }() 22 | 23 | let blurView: UIVisualEffectView = { 24 | let blurEffect = UIBlurEffect(style: .dark) 25 | let view = UIVisualEffectView(effect: blurEffect) 26 | 27 | return view 28 | }() 29 | 30 | let iconContainerView: UIView = { 31 | let view = UIView() 32 | view.layer.cornerRadius = 10 33 | view.layer.cornerCurve = .continuous 34 | view.layer.borderColor = UIColor(white: 1, alpha: 0.8).cgColor 35 | view.layer.borderWidth = 1.5 36 | view.clipsToBounds = true 37 | 38 | return view 39 | }() 40 | 41 | let iconImageView: UIImageView = { 42 | let view = UIImageView() 43 | view.tintColor = .white 44 | view.contentMode = .scaleAspectFit 45 | 46 | return view 47 | }() 48 | 49 | var isGradientSetted: Bool = false 50 | 51 | let gradientLayer: CAGradientLayer = { 52 | let layer = CAGradientLayer() 53 | layer.startPoint = CGPoint(x: 0, y: 0) 54 | layer.endPoint = CGPoint(x: 1, y: 1) 55 | 56 | return layer 57 | }() 58 | 59 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 60 | super.init(style: style, reuseIdentifier: reuseIdentifier) 61 | selectionStyle = .none 62 | 63 | backgroundColor = .clear 64 | contentView.addSubview(containerView) 65 | 66 | containerView.addSubview(iconContainerView) 67 | containerView.addSubview(titleLabel) 68 | containerView.layer.insertSublayer(gradientLayer, at: 0) 69 | 70 | iconContainerView.addSubview(blurView) 71 | iconContainerView.addSubview(iconImageView) 72 | 73 | setupConstraints() 74 | } 75 | 76 | override func layoutSubviews() { 77 | super.layoutSubviews() 78 | 79 | if !isGradientSetted && containerView.bounds != .zero { 80 | gradientLayer.frame = containerView.bounds 81 | isGradientSetted = true 82 | } 83 | } 84 | 85 | required init?(coder: NSCoder) { 86 | fatalError("init(coder:) has not been implemented") 87 | } 88 | 89 | private func setupConstraints() { 90 | containerView.snp.makeConstraints { make in 91 | make.left.right.equalToSuperview().inset(16.0) 92 | make.top.bottom.equalToSuperview().inset(10.0) 93 | } 94 | 95 | iconContainerView.snp.makeConstraints { make in 96 | make.centerY.equalToSuperview() 97 | make.left.equalToSuperview().offset(20.0) 98 | make.size.equalTo(42.0) 99 | } 100 | 101 | blurView.snp.makeConstraints { make in 102 | make.edges.equalToSuperview() 103 | } 104 | 105 | iconImageView.snp.makeConstraints { make in 106 | make.edges.equalToSuperview().inset(6.0) 107 | } 108 | 109 | titleLabel.snp.makeConstraints { make in 110 | make.left.equalTo(iconContainerView.snp.right).offset(12.0) 111 | make.right.equalToSuperview().offset(-12.0) 112 | make.bottom.top.equalToSuperview().inset(20.0) 113 | make.centerY.equalToSuperview() 114 | } 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /UIExamples/Modules/ExamplesList/ExamplesListController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import SwiftUI 3 | import SnapKit 4 | 5 | class ExamplesListController: UIViewController { 6 | 7 | typealias Model = ExampleListModel 8 | 9 | enum Section: Int, CaseIterable { 10 | case swiftUI = 0 11 | 12 | init(at indexPath: IndexPath) { 13 | self.init(rawValue: indexPath.section)! 14 | } 15 | 16 | init(_ section: Int) { 17 | self.init(rawValue: section)! 18 | } 19 | 20 | var title: String { 21 | switch self { 22 | case .swiftUI: return "SwiftUI" 23 | } 24 | } 25 | } 26 | 27 | private let model = Model.initial 28 | 29 | private var mainView: ExamplesListView { view as! ExamplesListView } 30 | override func loadView() { view = ExamplesListView() } 31 | 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | 35 | navigationItem.title = "Examples" 36 | navigationController?.navigationBar.prefersLargeTitles = true 37 | 38 | mainView.tableView.dataSource = self 39 | mainView.tableView.delegate = self 40 | } 41 | 42 | override func viewDidLayoutSubviews() { 43 | super.viewDidLayoutSubviews() 44 | 45 | mainView.tableView.reloadData() 46 | } 47 | 48 | private func handleSwiftUITap(at indexPath: IndexPath) { 49 | let view = model.swiftUIExamples[indexPath.row].view 50 | let vc = UIHostingController(rootView: view) 51 | 52 | present(vc, animated: true) 53 | } 54 | 55 | // maybe give model to cell for configure 56 | private func configureSwiftUICell(_ cell: ExampleCell, at indexPath: IndexPath) { 57 | let example = model.swiftUIExamples[indexPath.row] 58 | 59 | cell.titleLabel.text = example.title 60 | cell.iconImageView.image = UIImage(systemName: example.sfSymbol) 61 | cell.gradientLayer.colors = [example.background.cgColor, example.background.offset(by: 0.2).cgColor] 62 | } 63 | } 64 | 65 | // MARK: - UITableViewDelegate & UITableViewDataSource 66 | 67 | extension ExamplesListController: UITableViewDelegate, UITableViewDataSource { 68 | func numberOfSections(in tableView: UITableView) -> Int { 69 | return Section.allCases.count 70 | } 71 | 72 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 73 | switch Section(at: indexPath) { 74 | case .swiftUI: handleSwiftUITap(at: indexPath) 75 | } 76 | } 77 | 78 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 79 | switch Section(section) { 80 | case .swiftUI: 81 | return model.swiftUIExamples.count 82 | } 83 | } 84 | 85 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 86 | switch Section(at: indexPath) { 87 | case .swiftUI: 88 | let cell = tableView.dequeueReusableCell(withIdentifier: ExampleCell.description(), for: indexPath) as! ExampleCell 89 | configureSwiftUICell(cell, at: indexPath) 90 | 91 | return cell 92 | } 93 | } 94 | 95 | func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { 96 | let delete = UIContextualAction(style: .destructive, title: "Delete") { action, view, completion in 97 | 98 | } 99 | 100 | return UISwipeActionsConfiguration(actions: [delete]) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /UIExamples/Modules/ExamplesList/Model/ExampleListModel.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftUI 3 | 4 | struct ExampleListModel { 5 | 6 | private var _swiftUIExamples: [SwiftUIExample] 7 | var swiftUIExamples: [SwiftUIExample] { return _swiftUIExamples } 8 | 9 | // MARK: Initialization 10 | 11 | init(swiftUIExamples: [SwiftUIExample]) { 12 | _swiftUIExamples = swiftUIExamples 13 | } 14 | 15 | static var initial: ExampleListModel { 16 | return ExampleListModel(swiftUIExamples: [ 17 | SwiftUIExample( 18 | title: "Magic Button", 19 | background: UIColor(hex: 0xED9B3F), 20 | sfSymbol: "sparkles", 21 | view: MagicView() 22 | ), 23 | SwiftUIExample( 24 | title: "Loading View", 25 | background: UIColor(hex: 0xFA15FF), 26 | sfSymbol: "circle.dotted", 27 | view: LoadingView() 28 | ), 29 | SwiftUIExample( 30 | title: "Empty Grid View", 31 | background: UIColor(hex: 0x3BFF1B), 32 | sfSymbol: "grid", 33 | view: EmptyGridView() 34 | ), 35 | SwiftUIExample( 36 | title: "Notification Access v3", 37 | background: UIColor(hex: 0xFF961B), 38 | sfSymbol: "rectangle.stack", 39 | view: NotificationSetupView3() 40 | ), 41 | SwiftUIExample( 42 | title: "Notification Access v2", 43 | background: UIColor(hex: 0x840D98), 44 | sfSymbol: "rectangle.grid.1x2", 45 | view: NotificationSetupView2() 46 | ), 47 | SwiftUIExample( 48 | title: "Notification Access", 49 | background: UIColor(hex: 0xFF1B1B), 50 | sfSymbol: "app.badge", 51 | view: NotificationSetupView() 52 | ), 53 | SwiftUIExample( 54 | title: "Gallery Access", 55 | background: UIColor(hex: 0x1B24FF), 56 | sfSymbol: "lock.rectangle.on.rectangle.fill", 57 | view: GalleryAccessView() 58 | ), 59 | SwiftUIExample( 60 | title: "Nice Button Style", 61 | background: UIColor(hex: 0x0a84ff), 62 | sfSymbol: "cursorarrow", 63 | view: NiceButtonExample() 64 | ) 65 | ]) 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /UIExamples/Modules/ExamplesList/View/ExamplesListView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class ExamplesListView: UIView { 4 | 5 | let tableView: UITableView = { 6 | let view = UITableView() 7 | view.register(ExampleCell.self, forCellReuseIdentifier: ExampleCell.description()) 8 | view.backgroundColor = .clear 9 | view.separatorStyle = .none 10 | view.showsVerticalScrollIndicator = false 11 | 12 | return view 13 | }() 14 | 15 | override init(frame: CGRect) { 16 | super.init(frame: frame) 17 | backgroundColor = .systemBackground 18 | 19 | addSubview(tableView) 20 | 21 | setupConstraints() 22 | } 23 | 24 | required init?(coder: NSCoder) { 25 | fatalError("init(coder:) has not been implemented") 26 | } 27 | 28 | private func setupConstraints() { 29 | tableView.snp.makeConstraints { make in 30 | make.edges.equalToSuperview() 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /UIExamples/Modules/GalleryAccess/View/GalleryAccessView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct GalleryAccessView: View { 4 | @Environment(\.dismiss) private var dismiss 5 | let blurHeight = 150.0 6 | let openSettingsButtonHeight = 48.0 7 | let blurTint: UIColor = .secondarySystemBackground.withAlphaComponent(0) 8 | let impactFeedback = UIImpactFeedbackGenerator(style: .soft) 9 | 10 | var body: some View { 11 | GeometryReader { geometry in 12 | ZStack(alignment: .topLeading) { 13 | VStack { 14 | GeometryReader { scrollViewGeometry in 15 | ScrollView { 16 | VStack(spacing: 18) { 17 | Spacer(minLength: geometry.safeAreaInsets.top) 18 | 19 | Text(LocalizedStringKey("access-restricted_title")) 20 | .font(.system(size: 34, weight: .bold)) 21 | .multilineTextAlignment(.center) 22 | .padding(.top, 20) 23 | .padding(.bottom, -2) 24 | .fixedSize(horizontal: false, vertical: true) 25 | 26 | StepView(type: .first) { } 27 | .padding(.horizontal, 0) 28 | 29 | StepView(type: .second) { 30 | SettingsView(imageName: "app-photos", primaryText: LocalizedStringKey("access-restricted_settings_app-photos"), secondaryText: LocalizedStringKey("access-restricted_settings_app-photos_none")) 31 | .clipShape(RoundedRectangle(cornerRadius: 14)) 32 | .overlay { 33 | RoundedRectangle(cornerRadius: 14) 34 | .stroke(Color.primary.opacity(0.2), lineWidth: 0.5) 35 | } 36 | } 37 | .padding(.horizontal, 0) 38 | 39 | StepView(type: .third) { 40 | SettingsTableView() 41 | .clipShape(RoundedRectangle(cornerRadius: 14)) 42 | .overlay { 43 | RoundedRectangle(cornerRadius: 14) 44 | .stroke(Color.primary.opacity(0.2), lineWidth: 0.5) 45 | } 46 | } 47 | .padding(.horizontal, 0) 48 | 49 | StepView(type: .fourth) { } 50 | .padding(.horizontal, 0) 51 | 52 | Spacer().frame(height: openSettingsButtonHeight + 20) 53 | } 54 | .padding(.horizontal, 40) 55 | .padding(.bottom, blurHeight) 56 | .frame(width: scrollViewGeometry.size.width) 57 | .frame(minHeight: scrollViewGeometry.size.height) 58 | } 59 | .scrollIndicators(.never) 60 | } 61 | } 62 | 63 | ZStack() { 64 | BlurView(colorTint: blurTint) 65 | .mask({ 66 | LinearGradient( 67 | gradient: Gradient(colors: [.clear, .black]), 68 | startPoint: UnitPoint(x: 0, y: 0.1), 69 | endPoint: UnitPoint(x: 0, y: 0.4) 70 | ) 71 | }) 72 | .frame(height: blurHeight + geometry.safeAreaInsets.bottom) 73 | 74 | 75 | Button(action: { 76 | if let url = URL(string: UIApplication.openSettingsURLString) { 77 | UIApplication.shared.open(url) 78 | } 79 | }) { 80 | Text(LocalizedStringKey("access-restricted_btn")) 81 | .foregroundColor(.white) 82 | } 83 | .buttonStyle(NiceButtonStyle(color: .blue)) 84 | .frame(height: openSettingsButtonHeight) 85 | .padding(.horizontal, 40) 86 | .offset(y: -geometry.safeAreaInsets.bottom/2 + (blurHeight - openSettingsButtonHeight)/2 - 40) 87 | } 88 | .offset(y: geometry.size.height + geometry.safeAreaInsets.top - blurHeight) 89 | 90 | Button { 91 | impactFeedback.impactOccurred() 92 | dismiss() 93 | } label: { } 94 | .buttonStyle(CircleSmallButton(icon: "xmark")) 95 | .offset(y: geometry.safeAreaInsets.top) 96 | 97 | } 98 | .edgesIgnoringSafeArea(.all) 99 | } 100 | } 101 | } 102 | 103 | private func openSettings() { 104 | guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else { return } 105 | if UIApplication.shared.canOpenURL(settingsURL) { 106 | UIApplication.shared.open(settingsURL) 107 | } 108 | } 109 | 110 | struct RoundedCorner: Shape { 111 | var radius: CGFloat = .infinity 112 | var corners: UIRectCorner = .allCorners 113 | 114 | func path(in rect: CGRect) -> Path { 115 | let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) 116 | return Path(path.cgPath) 117 | } 118 | } 119 | 120 | struct StepView: View { 121 | let type: StepType 122 | let settingsView: Content 123 | 124 | enum StepType: Int { 125 | case first = 1 126 | case second 127 | case third 128 | case fourth 129 | 130 | var index: String { 131 | return self.rawValue.description 132 | } 133 | 134 | var stringKey: LocalizedStringKey { 135 | switch self { 136 | case .first: return LocalizedStringKey("access-restricted_step-1") 137 | case .second: return LocalizedStringKey("access-restricted_step-2") 138 | case .third: 139 | if #available(iOS 17.0, *) { 140 | return LocalizedStringKey("access-restricted_step-3-ios17") 141 | } 142 | 143 | return LocalizedStringKey("access-restricted_step-3") 144 | case .fourth: return LocalizedStringKey("access-restricted_step-4") 145 | } 146 | } 147 | } 148 | 149 | init(type: StepType, @ViewBuilder settingsView: () -> Content) { 150 | self.type = type 151 | self.settingsView = settingsView() 152 | } 153 | 154 | var body: some View { 155 | VStack { 156 | HStack(alignment: .top, spacing: 10) { 157 | Circle() 158 | .foregroundColor(.blue) 159 | .frame(width: 24, height: 24) 160 | .overlay( 161 | Text(type.index) 162 | .foregroundColor(.white) 163 | .font(.system(size: 16, weight: .semibold, design: .rounded)) 164 | ) 165 | 166 | 167 | VStack(alignment: .leading) { 168 | Text(type.stringKey) 169 | .padding(.top, 2) 170 | .font(.system(size: 18)) 171 | .multilineTextAlignment(.leading) 172 | .fixedSize(horizontal: false, vertical: true) 173 | 174 | settingsView 175 | } 176 | } 177 | .frame(maxWidth: .infinity, alignment: .leading) 178 | 179 | } 180 | } 181 | } 182 | 183 | struct SettingsTableView: View { 184 | var tableTitle: String { 185 | if #available(iOS 17.0, *) { 186 | return "access-restricted_settings_tableview-group_title-ios17" 187 | } else { 188 | return "access-restricted_settings_tableview-group_title" 189 | } 190 | } 191 | 192 | var body: some View { 193 | VStack(alignment: .leading, spacing: 0) { 194 | Text(LocalizedStringKey(tableTitle)) 195 | .font(.system(size: 11, weight: .regular)) 196 | .foregroundColor(.secondary) 197 | .padding(.top, 10) 198 | .padding(.leading, 14) 199 | .padding(.bottom, 8) 200 | .textCase(.uppercase) 201 | 202 | if #available(iOS 17.0, *) { 203 | TableRowView( 204 | primaryText: "access-restricted_settings_tableview-group_none" 205 | ) 206 | } else { 207 | TableRowView( 208 | primaryText: "access-restricted_settings_tableview-group_selected" 209 | ) 210 | } 211 | 212 | SeparatorView() 213 | 214 | if #available(iOS 17.0, *) { 215 | TableRowView( 216 | primaryText: "access-restricted_settings_tableview-group_selected-ios17", 217 | subtitle: "access-restricted_settings_tableview-group_selected-ios17_subtitle" 218 | ) 219 | } else { 220 | TableRowView( 221 | primaryText: "access-restricted_settings_tableview-group_all", 222 | secondarySymbol: "checkmark" 223 | ) 224 | } 225 | 226 | SeparatorView() 227 | 228 | if #available(iOS 17.0, *) { 229 | TableRowView( 230 | primaryText: "access-restricted_settings_tableview-group_all-ios17", 231 | secondarySymbol: "checkmark", 232 | subtitle: "access-restricted_settings_tableview-group_all-ios17_subtitle" 233 | ) 234 | } else { 235 | TableRowView( 236 | primaryText: "access-restricted_settings_tableview-group_none" 237 | ) 238 | } 239 | } 240 | .background(Color(.systemGroupedBackground)) 241 | } 242 | } 243 | 244 | struct TableRowView: View { 245 | var primaryText: LocalizedStringKey 246 | var secondarySymbol: String? 247 | var subtitle: LocalizedStringKey? 248 | 249 | init(primaryText: String, secondarySymbol: String? = nil, subtitle: String? = nil) { 250 | self.primaryText = LocalizedStringKey(primaryText) 251 | self.secondarySymbol = secondarySymbol 252 | 253 | if let subtitle { 254 | self.subtitle = LocalizedStringKey(subtitle) 255 | } 256 | } 257 | 258 | var body: some View { 259 | HStack { 260 | VStack(alignment: .leading) { 261 | HStack { 262 | Text(primaryText) 263 | .font(.system(size: 16, weight: .regular)) 264 | .padding(.leading, 14) 265 | 266 | Spacer() 267 | } 268 | 269 | if let subtitle { 270 | Text(subtitle) 271 | .font(.system(size: 11, weight: .regular)) 272 | .foregroundColor(.secondary) 273 | .padding(.leading, 14) 274 | .offset(y: 2) 275 | .padding(.bottom, 1) 276 | } 277 | } 278 | 279 | if let secondarySymbol { 280 | Image(systemName: secondarySymbol) 281 | .font(.system(size: 16, weight: .medium)) 282 | .foregroundColor(.blue) 283 | .padding(.trailing, 14) 284 | .padding(.bottom, 2) 285 | } 286 | } 287 | .padding(.vertical, 10) 288 | .background(Color(.secondarySystemGroupedBackground)) 289 | } 290 | } 291 | 292 | struct SeparatorView: View { 293 | var body: some View { 294 | ZStack{ 295 | Rectangle() 296 | .foregroundColor(Color(.secondarySystemGroupedBackground)) 297 | .frame(height: 0.33) 298 | Rectangle() 299 | .foregroundColor(Color.gray.opacity(0.8)) 300 | .frame(maxWidth: .infinity) 301 | .frame(height: 0.33) 302 | .padding(.leading, 14) 303 | }} 304 | } 305 | 306 | 307 | 308 | struct SettingsView: View { 309 | var imageName: String 310 | var primaryText: LocalizedStringKey 311 | var secondaryText: LocalizedStringKey 312 | 313 | var body: some View { 314 | HStack { 315 | Image(imageName) 316 | .resizable() 317 | .aspectRatio(contentMode: .fill) 318 | .frame(width: 22, height: 22) 319 | 320 | Text(primaryText) 321 | .font(.system(size: 16, weight: .regular)) 322 | 323 | Spacer() 324 | 325 | HStack(spacing: 6) { 326 | Text(secondaryText) 327 | .font(.system(size: 16, weight: .regular)) 328 | .foregroundColor(.secondary) 329 | 330 | Image(systemName: "chevron.right") 331 | .foregroundColor(.secondary) 332 | .font(.system(size: 14, weight: .regular)) 333 | } 334 | } 335 | .padding(.vertical, 10) 336 | .padding(.horizontal, 10) 337 | .background(Color(.secondarySystemGroupedBackground)) 338 | } 339 | } 340 | 341 | 342 | struct GalleryAccessView_Previews: PreviewProvider { 343 | static var previews: some View { 344 | GalleryAccessView() 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /UIExamples/Modules/Loading/Model/LoadingParticle.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct LoadingParticle: Identifiable { 4 | let id = UUID() 5 | let color: Color 6 | var position: CGPoint 7 | var size: CGFloat 8 | var opacity: Double 9 | var scale: CGFloat 10 | } 11 | -------------------------------------------------------------------------------- /UIExamples/Modules/Loading/View/LoadingView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct LoadingView: View { 4 | var body: some View { 5 | ZStack { 6 | StarrySkyView() 7 | 8 | VStack { 9 | ProgressView() 10 | .progressViewStyle(CircularProgressViewStyle()) 11 | .scaleEffect(1.5) 12 | .tint(.primary) 13 | .padding(.bottom, -2) 14 | 15 | Text(LocalizedStringKey("Loading View")) 16 | .font(.system(size: 34, weight: .bold)) 17 | .padding(.horizontal, 40) 18 | .padding(.bottom, -10) 19 | .foregroundColor(.primary) 20 | .multilineTextAlignment(.center) 21 | } 22 | } 23 | .edgesIgnoringSafeArea(.all) 24 | } 25 | } 26 | 27 | #Preview { 28 | LoadingView() 29 | } 30 | -------------------------------------------------------------------------------- /UIExamples/Modules/Loading/View/StarrySkyView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct StarrySkyView: View { 4 | @State private var particles: [LoadingParticle] = [] 5 | @State private var bigParticles: [LoadingParticle] = [] 6 | 7 | private let particleCount = 100 8 | private let bigParticleCount = 10 9 | private let colors: Set = [.yellow, .blue, .pink, .brown, .red, .green, .orange, .cyan, .mint, .indigo] 10 | 11 | var body: some View { 12 | GeometryReader { geometry in 13 | ZStack { 14 | ForEach(particles) { particle in 15 | Circle() 16 | .foregroundColor(particle.color) 17 | .opacity(particle.opacity) 18 | .frame(width: particle.size, height: particle.size) 19 | .scaleEffect(particle.scale) 20 | .position(particle.position) 21 | .onAppear { 22 | Task(priority: .high) { 23 | positionParticle(particle, bounds: geometry.size) 24 | animateParticle(particle) 25 | } 26 | } 27 | } 28 | 29 | ForEach(bigParticles) { particle in 30 | Circle() 31 | .foregroundColor(particle.color) 32 | .opacity(particle.opacity) 33 | .blur(radius: 30) 34 | .frame(width: particle.size, height: particle.size) 35 | .position(particle.position) 36 | .onAppear { 37 | animateBigParticle(particle, bounds: geometry.size) 38 | } 39 | } 40 | } 41 | .onAppear { 42 | bigParticles = generateBigParticles(in: geometry.size) 43 | particles = generateParticles(in: geometry.size) 44 | } 45 | } 46 | } 47 | 48 | private func generateBigParticles(in size: CGSize) -> [LoadingParticle] { 49 | var particles: [LoadingParticle] = [] 50 | 51 | for _ in 0.. [LoadingParticle] { 71 | var particles: [LoadingParticle] = [] 72 | 73 | for _ in 0.. Animation { 159 | Animation.easeInOut(duration: Double.random(in: 1...2)) 160 | } 161 | } 162 | 163 | #Preview { 164 | StarrySkyView() 165 | } 166 | -------------------------------------------------------------------------------- /UIExamples/Modules/MagicButton/Model/ButtonParticle.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ButtonParticle: Hashable { 4 | enum ParticleType { 5 | case up 6 | case down 7 | } 8 | 9 | enum OpacityType { 10 | case appearing 11 | case disappearing 12 | } 13 | 14 | enum ShapeType { 15 | case circle 16 | case star 17 | } 18 | 19 | enum RotationType { 20 | case onAxis 21 | case offAxis 22 | } 23 | 24 | let type: ParticleType 25 | let shape: ShapeType 26 | let rotationType: RotationType 27 | var rotation: Angle 28 | var opacityType: OpacityType 29 | var dalay: CGFloat 30 | var x: CGFloat 31 | var y: CGFloat 32 | var speed: CGFloat 33 | var easingAngle: Angle 34 | var realAngle: Angle 35 | var radius: CGFloat 36 | var color: Color 37 | var opacity: Double 38 | var width: CGFloat 39 | var height: CGFloat 40 | } 41 | -------------------------------------------------------------------------------- /UIExamples/Modules/MagicButton/Shared/ParticleAnimator.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | class ParticleAnimator: ObservableObject { 4 | typealias ParticleType = ButtonParticle.ParticleType 5 | 6 | @Published var particles: [ButtonParticle] = [] 7 | @Published var rect: CGRect = .zero { 8 | didSet { 9 | particles.append( 10 | contentsOf: generateParticles(count: 40, around: rect, type: .down) 11 | ) 12 | particles.append( 13 | contentsOf: generateParticles(count: 12, around: rect, type: .up) 14 | ) 15 | } 16 | } 17 | @Published var isPressed: Bool = false 18 | 19 | private var isAnimating: Bool = false 20 | private var converter = ProgressConverter(minValue: 0, maxValue: 360) 21 | 22 | @MainActor 23 | func animate() { 24 | Task { 25 | isAnimating.toggle() 26 | 27 | while isAnimating { 28 | try await Task.sleep(for: .seconds(1.0/60.0)) 29 | updateParticles() 30 | } 31 | } 32 | } 33 | 34 | private func updateParticles() { 35 | for index in 0.. 0 { 42 | particles[index].dalay -= isPressed ? particles[index].dalay : 0.016 43 | } else { 44 | updateOpacityParticle(at: index) 45 | moveParticle(at: index) 46 | } 47 | } 48 | 49 | func updateOpacityParticle(at index: Int) { 50 | switch particles[index].opacityType { 51 | case .appearing: 52 | particles[index].opacity = min(1, particles[index].opacity + 0.016) 53 | case .disappearing: 54 | particles[index].opacity -= 0.016 55 | } 56 | 57 | let maxDegree: Double = particles[index].type == .down ? 45 : 90 58 | 59 | if particles[index].realAngle.degrees > maxDegree { 60 | particles[index].opacityType = .disappearing 61 | } 62 | 63 | if particles[index].opacity <= 0 { 64 | particles[index].opacityType = .appearing 65 | resetParticlePosition(at: index) 66 | } 67 | } 68 | 69 | private func resetParticlePosition(at index: Int) { 70 | particles[index].easingAngle = .zero 71 | particles[index].realAngle = .zero 72 | particles[index].dalay = CGFloat.random(in: 0.1...6) 73 | particles[index].radius = particles[index].type == .down ? .random(in: -rect.width/2...rect.width/2) : .random(in: -rect.height/3...rect.height/3) 74 | } 75 | 76 | private func moveParticle(at index: Int) { 77 | let speedMulti: CGFloat = particles[index].type == .down ? 2 : 3 78 | let speed = isPressed ? particles[index].speed * speedMulti : particles[index].speed 79 | let newAngle = particles[index].realAngle + Angle.degrees(0.016 * speed) 80 | let easingAngle: Angle = .degrees(converter.convertWithEaseOut(newAngle.degrees)) 81 | 82 | particles[index].realAngle = newAngle 83 | particles[index].easingAngle = easingAngle 84 | particles[index].rotation = particles[index].rotationType == .onAxis ? easingAngle : -easingAngle 85 | 86 | if particles[index].radius > 0 { 87 | particles[index].radius += (0.016 * speed)/3 88 | } else { 89 | particles[index].radius -= (0.016 * speed)/3 90 | } 91 | 92 | particles[index].x = particles[index].radius * cos(particles[index].easingAngle.radians) 93 | particles[index].y = particles[index].radius/2.4 * sin(particles[index].easingAngle.radians) 94 | } 95 | 96 | private func generateParticles(count: Int, around rect: CGRect, type: ParticleType) -> [ButtonParticle] { 97 | var particles: [ButtonParticle] = [] 98 | typealias ShapeType = ButtonParticle.ShapeType 99 | typealias RotationType = ButtonParticle.RotationType 100 | 101 | for _ in 0.., excludeRange: ClosedRange) -> CGFloat { 143 | while true { 144 | let randomValue = CGFloat.random(in: range) 145 | if excludeRange.contains(randomValue) { 146 | continue 147 | } 148 | 149 | return randomValue 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /UIExamples/Modules/MagicButton/Shared/ParticleStyleBounce.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct MagicStyleBounce: PreferenceKey { 4 | static var defaultValue: CGRect = .zero 5 | 6 | static func reduce(value: inout CGRect, nextValue: () -> CGRect) { 7 | value = nextValue() 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /UIExamples/Modules/MagicButton/Shared/ProgressConverter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | class ProgressConverter { 4 | let minValue: Double 5 | let maxValue: Double 6 | 7 | init(minValue: Double, maxValue: Double) { 8 | self.minValue = minValue 9 | self.maxValue = maxValue 10 | } 11 | 12 | func convertWithEaseOut(_ value: Double) -> Double { 13 | let progress = progress(forValue: value) 14 | let converted = easeOutQuad(progress) 15 | let value = self.value(forProgress: converted) 16 | 17 | return value 18 | } 19 | 20 | private func progress(forValue value: Double) -> Double { 21 | (value - minValue) / (maxValue - minValue) 22 | } 23 | 24 | private func value(forProgress progress: Double) -> Double { 25 | minValue + (progress * (maxValue - minValue)) 26 | } 27 | 28 | private func easeOutQuad(_ x: Double) -> Double { 29 | 1 - pow(1 - x, 4) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /UIExamples/Modules/MagicButton/Style/MagicStyle.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct MagicStyle: ButtonStyle { 4 | @State var color: Color = .clear 5 | @State var gradientColors: [Color] = [] 6 | @StateObject private var animator = ParticleAnimator() 7 | @State private var animateGradient: Bool = false 8 | 9 | func makeBody(configuration: Configuration) -> some View { 10 | ZStack(alignment: .center) { 11 | ForEach(animator.particles.filter { $0.type == .down }, id: \.self) { 12 | ButtonParticleView(particle: $0) 13 | } 14 | 15 | SetupConfiguration(configuration) 16 | .onPreferenceChange(MagicStyleBounce.self) { rect in 17 | animator.rect = rect 18 | } 19 | 20 | ForEach(animator.particles.filter { $0.type == .up }, id: \.self) { 21 | ButtonParticleView(particle: $0) 22 | .blendMode(.softLight) 23 | } 24 | } 25 | .onAppear { 26 | animator.animate() 27 | } 28 | .onDisappear { 29 | animator.animate() 30 | } 31 | } 32 | 33 | private func SetupConfiguration(_ configuration: Configuration) -> some View { 34 | configuration.label 35 | .onChange(of: configuration.isPressed, { oldValue, newValue in 36 | animator.isPressed = newValue 37 | }) 38 | .foregroundColor(.white) 39 | .font(.system(size: 18, weight: .semibold)) 40 | .padding(.top, configuration.isPressed ? 14 : 13) 41 | .padding(.bottom, configuration.isPressed ? 13 : 14) 42 | .padding(.horizontal, 30) 43 | .background(color) 44 | .background( 45 | LinearGradient( 46 | colors: gradientColors, 47 | startPoint: .leading, 48 | endPoint: .trailing 49 | ) 50 | .hueRotation(.degrees(animateGradient ? 45 : 0)) 51 | .onAppear { 52 | withAnimation(.easeInOut(duration: 3).repeatForever()) { 53 | animateGradient.toggle() 54 | } 55 | } 56 | ) 57 | .overlay(configuration.isPressed ? Color.black.opacity(0.15) : Color.clear) 58 | .clipShape(.rect(cornerRadius: configuration.isPressed ? 16 : 14)) 59 | .scaleEffect(configuration.isPressed ? 0.98 : 1.0) 60 | .shadow( 61 | color: configuration.isPressed ? Color.black.opacity(0.1) : Color.clear, 62 | radius: configuration.isPressed ? 10 : 0, 63 | x: configuration.isPressed ? 0 : 0, 64 | y: configuration.isPressed ? -3 : 0 65 | ) 66 | .animation(.spring().speed(1.5), value: configuration.isPressed) 67 | .background( 68 | GeometryReader { 69 | Color.clear.preference( 70 | key: MagicStyleBounce.self, 71 | value: $0.frame(in: .local) 72 | ) 73 | } 74 | ) 75 | } 76 | } 77 | 78 | #Preview { 79 | VStack { 80 | Button(action: {}, label: { 81 | Text("Create with AI") 82 | .foregroundStyle(.white) 83 | }) 84 | .buttonStyle(MagicStyle(gradientColors: [.blue, .purple])) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /UIExamples/Modules/MagicButton/Subview/ButtonParticleView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ButtonParticleView: View { 4 | var particle: ButtonParticle 5 | 6 | var body: some View { 7 | switch particle.shape { 8 | case .circle: 9 | Circle() 10 | .frame(width: particle.width/2, height: particle.height/2) 11 | .foregroundColor(particle.color) 12 | .offset(x: particle.x, y: particle.y) 13 | .opacity(particle.opacity) 14 | case .star: 15 | CustomShape() 16 | .frame(width: particle.width, height: particle.height) 17 | .foregroundColor(particle.color) 18 | .rotationEffect(particle.rotation) 19 | .offset(x: particle.x, y: particle.y) 20 | .opacity(particle.opacity) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /UIExamples/Modules/MagicButton/Subview/CustomShape.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct CustomShape: Shape { 4 | func path(in rect: CGRect) -> Path { 5 | var path = Path() 6 | 7 | path.move(to: CGPoint(x: 1.9148, y: 3.29345)) 8 | path.addCurve(to: CGPoint(x: 2.35976, y: 3.10447), control1: CGPoint(x: 2.00836, y: 3.24822), control2: CGPoint(x: 2.14821, y: 3.19101)) 9 | path.addLine(to: CGPoint(x: 2.48463, y: 3.05086)) 10 | path.addCurve(to: CGPoint(x: 2.90854, y: 2.62695), control1: CGPoint(x: 2.66863, y: 2.95992), control2: CGPoint(x: 2.8176, y: 2.81095)) 11 | path.addLine(to: CGPoint(x: 3.16464, y: 2.00709)) 12 | path.addLine(to: CGPoint(x: 3.63544, y: 0.856262)) 13 | path.addLine(to: CGPoint(x: 4.3277, y: 0.856262)) 14 | path.addLine(to: CGPoint(x: 4.79849, y: 2.00709)) 15 | path.addCurve(to: CGPoint(x: 5.00099, y: 2.50208), control1: CGPoint(x: 4.86211, y: 2.16259), control2: CGPoint(x: 4.91809, y: 2.29944)) 16 | path.addCurve(to: CGPoint(x: 5.4785, y: 3.05086), control1: CGPoint(x: 5.14554, y: 2.81095), control2: CGPoint(x: 5.2945, y: 2.95992)) 17 | path.addCurve(to: CGPoint(x: 5.60337, y: 3.10446), control1: CGPoint(x: 5.50815, y: 3.06551), control2: CGPoint(x: 5.53989, y: 3.07849)) 18 | path.addCurve(to: CGPoint(x: 6.04833, y: 3.29345), control1: CGPoint(x: 5.81492, y: 3.19101), control2: CGPoint(x: 5.95477, y: 3.24822)) 19 | path.addLine(to: CGPoint(x: 6.11469, y: 3.31364)) 20 | path.addLine(to: CGPoint(x: 7.24919, y: 3.77775)) 21 | path.addLine(to: CGPoint(x: 6.13122, y: 4.92737)) 22 | path.addCurve(to: CGPoint(x: 5.60337, y: 5.14331), control1: CGPoint(x: 5.96685, y: 4.99461), control2: CGPoint(x: 5.82416, y: 5.05298)) 23 | path.addCurve(to: CGPoint(x: 5.05459, y: 5.62082), control1: CGPoint(x: 5.2945, y: 5.28786), control2: CGPoint(x: 5.14554, y: 5.43682)) 24 | path.addCurve(to: CGPoint(x: 4.81779, y: 6.17858), control1: CGPoint(x: 5.03994, y: 5.65046), control2: CGPoint(x: 4.91809, y: 5.94832)) 25 | path.addLine(to: CGPoint(x: 4.79849, y: 6.24068)) 26 | path.addLine(to: CGPoint(x: 4.3277, y: 7.39151)) 27 | path.addLine(to: CGPoint(x: 3.63544, y: 7.39151)) 28 | path.addLine(to: CGPoint(x: 3.16464, y: 6.24068)) 29 | path.addCurve(to: CGPoint(x: 2.96215, y: 5.74569), control1: CGPoint(x: 3.10103, y: 6.08518), control2: CGPoint(x: 3.04504, y: 5.94833)) 30 | path.addCurve(to: CGPoint(x: 2.48463, y: 5.19691), control1: CGPoint(x: 2.8176, y: 5.43682), control2: CGPoint(x: 2.66863, y: 5.28786)) 31 | path.addCurve(to: CGPoint(x: 2.35977, y: 5.14331), control1: CGPoint(x: 2.45499, y: 5.18226), control2: CGPoint(x: 2.42325, y: 5.16928)) 32 | path.addCurve(to: CGPoint(x: 1.9148, y: 3.29345), control1: CGPoint(x: 2.13898, y: 5.05299), control2: CGPoint(x: 1.99628, y: 4.99461)) 33 | path.addLine(to: CGPoint(x: 1.84844, y: 3.31364)) 34 | path.addLine(to: CGPoint(x: 0.713943, y: 3.77775)) 35 | path.addLine(to: CGPoint(x: 1.83191, y: 4.92737)) 36 | path.closeSubpath() 37 | 38 | return path 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /UIExamples/Modules/MagicButton/View/MagicView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct MagicView: View { 4 | var body: some View { 5 | VStack { 6 | Button(action: { }) { 7 | Text("Start with AI") 8 | .multilineTextAlignment(.center) 9 | .foregroundStyle(.white) 10 | } 11 | .buttonStyle(MagicStyle(gradientColors: [.blue, .purple])) 12 | } 13 | } 14 | } 15 | 16 | #Preview { 17 | VStack { 18 | MagicView() 19 | } 20 | .padding() 21 | } 22 | -------------------------------------------------------------------------------- /UIExamples/Modules/NiceButton/NiceButtonStyle/NiceButtonStyle.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct NiceButtonStyle: ButtonStyle { 4 | @State var color: Color 5 | 6 | func makeBody(configuration: Configuration) -> some View { 7 | configuration.label 8 | .foregroundColor(.white) 9 | .font(.system(size: 18, weight: .semibold)) 10 | .padding(.top, configuration.isPressed ? 14 : 13) 11 | .padding(.bottom, configuration.isPressed ? 13 : 14) 12 | .frame(maxWidth: .infinity) 13 | .background(color) 14 | .overlay( 15 | configuration.isPressed ? Color.black.opacity(0.15) : Color.clear 16 | ) 17 | .cornerRadius(configuration.isPressed ? 16 : 14) 18 | .scaleEffect(configuration.isPressed ? 0.98 : 1.0) 19 | .shadow(color: configuration.isPressed ? Color.black.opacity(0.1) : Color.clear, 20 | radius: configuration.isPressed ? 10 : 0, 21 | x: configuration.isPressed ? 0 : 0, 22 | y: configuration.isPressed ? -3 : 0) 23 | .animation(.spring().speed(1.5), value: configuration.isPressed) 24 | } 25 | } 26 | 27 | struct NiceButtonStyle_Previews: PreviewProvider { 28 | static var previews: some View { 29 | Button("Nice Button", action: {}) 30 | .buttonStyle(NiceButtonStyle(color: .blue)) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /UIExamples/Modules/NiceButton/View/NiceButtonExample.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct NiceButtonExample: View { 4 | @Environment(\.dismiss) private var dismiss 5 | @State private var isPresented = false 6 | 7 | let impactFeedback = UIImpactFeedbackGenerator(style: .soft) 8 | 9 | var body: some View { 10 | GeometryReader { geometry in 11 | Button { 12 | dismiss() 13 | } label: { } 14 | .buttonStyle(CircleSmallButton(icon: "xmark")) 15 | .offset(y: geometry.safeAreaInsets.top) 16 | 17 | Button(action: { 18 | impactFeedback.impactOccurred() 19 | }) { 20 | Text("Button") 21 | .foregroundColor(.white) 22 | } 23 | .buttonStyle(NiceButtonStyle(color: .blue)) 24 | .offset(y: geometry.size.height/2) 25 | .padding(.horizontal, 40) 26 | } 27 | } 28 | } 29 | 30 | struct NiceButtonExample_Previews: PreviewProvider { 31 | static var previews: some View { 32 | NiceButtonExample() 33 | } 34 | } 35 | 36 | struct NewView: View { 37 | var body: some View { 38 | Text("Это новая вьюха!") 39 | .navigationTitle("Example #2") 40 | .navigationBarTitleDisplayMode(.inline) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /UIExamples/Modules/Notifications/Model/NotificationSetupModel.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct NotificationSetupModel { 4 | private(set) var notificationFeatures: [NotificationFeature] = [] 5 | private(set) var notificationPushes: [NotificationPushModel] = [] 6 | 7 | init(features: [NotificationFeature], pushes: [NotificationPushModel]) { 8 | notificationFeatures = features 9 | notificationPushes = pushes 10 | } 11 | 12 | static var initial: NotificationSetupModel { 13 | let features: [NotificationFeature] = [ 14 | NotificationFeature( 15 | systemNamed: "shippingbox", 16 | title: "Order Status", 17 | subtitle: "Receive status alerts about your latest order activity." 18 | ), 19 | NotificationFeature( 20 | systemNamed: "calendar", 21 | title: "Session Reminders", 22 | subtitle: "Get reminders about your upcoming Today at Apple sessions." 23 | ), 24 | NotificationFeature( 25 | systemNamed: "bell.badge", 26 | title: "Announcements and Offers", 27 | subtitle: "Get information on new products, special store events, personalized recommendations and more." 28 | ), 29 | ] 30 | 31 | let pushes: [NotificationPushModel] = [ 32 | .init(icon: .init(named: "notification-push-app-icon"), userImage: .init(named: "notification-push-user"), contentImage: nil, title: "Seller", subtitle: "Sure, we can make it for you!", time: "now"), 33 | .init(icon: .init(named: "notification-push-app-icon"), userImage: nil, contentImage: .init(named: "notification-content-image"), title: "Delivered", subtitle: "Package with 4 items is under your door", time: "2h ago"), 34 | .init(icon: .init(named: "notification-push-app-icon"), userImage: nil, contentImage: nil, title: "Out for delivery", subtitle: "Your order will be delivered today", time: "3h ago"), 35 | .init(icon: .init(named: "notification-push-app-icon"), userImage: nil, contentImage: .init(named: "notification-content-image-2"), title: "Back in stock", subtitle: "Item from your wishlist is now available to buy", time: "Yesterday"), 36 | .init(icon: .init(named: "notification-push-app-icon"), userImage: nil, contentImage: nil, title: "Response from seller", subtitle: "Seller replied to your review", time: "Dec 13"), 37 | 38 | 39 | // .init(icon: .init(named: "notification-push-app-icon"), userImage: .init(named: "notification-push-user"), contentImage: nil, title: "Seller", subtitle: "Sure, we can make it for you!", time: "now"), 40 | // .init(icon: .init(named: "notification-push-app-icon"), userImage: nil, contentImage: .init(named: "notification-content-image"), title: "Delivered", subtitle: "Package with 4 items is under your door", time: "2h ago"), 41 | // .init(icon: .init(named: "notification-push-app-icon"), userImage: nil, contentImage: nil, title: "Out for delivery", subtitle: "Your order will be delivered today", time: "3h ago"), 42 | // .init(icon: .init(named: "notification-push-app-icon"), userImage: nil, contentImage: .init(named: "notification-content-image-2"), title: "Back in stock", subtitle: "Item from your wishlist is now available to buy", time: "Yesterday"), 43 | // .init(icon: .init(named: "notification-push-app-icon"), userImage: nil, contentImage: nil, title: "Response from seller", subtitle: "Seller replied to your review", time: "Dec 13") 44 | ] 45 | 46 | return NotificationSetupModel(features: features, pushes: pushes) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /UIExamples/Modules/Notifications/Subview/NotificationPushImageView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct NotificationPushImageView: View { 4 | var model: NotificationPushModel 5 | 6 | var body: some View { 7 | ZStack(alignment: .bottomTrailing) { 8 | if let userImage = model.userImage { 9 | Image(uiImage: userImage) 10 | .resizable() 11 | .aspectRatio(contentMode: .fill) 12 | .frame(width: 38, height: 38) 13 | .cornerRadius(38/2) 14 | .clipped() 15 | 16 | Image(uiImage: model.icon) 17 | .resizable() 18 | .aspectRatio(contentMode: .fill) 19 | .frame(width: 16, height: 16) 20 | .cornerRadius(4) 21 | .clipped() 22 | .offset(x: 3, y: 2.5) 23 | } else { 24 | Image(uiImage: model.icon) 25 | .resizable() 26 | .aspectRatio(contentMode: .fill) 27 | .frame(width: 38, height: 38) 28 | .cornerRadius(8) 29 | .clipped() 30 | } 31 | } 32 | } 33 | } 34 | 35 | struct NotificationPushImageView_Previews: PreviewProvider { 36 | static var previews: some View { 37 | NotificationPushImageView(model: .init()) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /UIExamples/Modules/Notifications/Subview/NotificationPushView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct NotificationPushView: View { 4 | @State private var pushHeight: CGFloat = 0 5 | @State private var contentHeight: CGFloat = 0 6 | @Binding var notStack: Bool 7 | 8 | let model: NotificationPushModel 9 | var index: Int 10 | 11 | var body: some View { 12 | ZStack(alignment: .topTrailing) { 13 | HStack(alignment: .center, spacing: 10) { 14 | NotificationPushImageView(model: model) 15 | 16 | VStack(alignment: .leading, spacing: 0) { 17 | Text(model.title) 18 | .font(.system(size: 15, weight: .semibold)) 19 | .multilineTextAlignment(.leading) 20 | .lineLimit(1) 21 | 22 | Text(model.subtitle) 23 | .padding(.all, 0) 24 | .font(.system(size: 15)) 25 | .multilineTextAlignment(.leading) 26 | .padding(.bottom, 2) 27 | .lineLimit(4) 28 | } 29 | 30 | if model.contentImage != nil { 31 | Spacer(minLength: 40) 32 | } else { 33 | Spacer() 34 | } 35 | } 36 | .background( 37 | GeometryReader { 38 | Color.clear.preference(key: ContentPreferenceKey.self, value: $0.size.height) 39 | } 40 | ) 41 | .onPreferenceChange(ContentPreferenceKey.self) { 42 | contentHeight = $0 43 | } 44 | .offset(y: (pushHeight - contentHeight)/2 - 14) 45 | .opacity(getContentOpacity()) 46 | 47 | 48 | VStack(alignment: .trailing, spacing: 6) { 49 | Text(model.time) 50 | .foregroundColor(.secondary) 51 | .multilineTextAlignment(.trailing) 52 | .font(.system(size: 13)) 53 | 54 | if let contentImage = model.contentImage { 55 | Image(uiImage: contentImage) 56 | .resizable() 57 | .aspectRatio(contentMode: .fill) 58 | .frame(width: 32, height: 32) 59 | .mask { 60 | RoundedRectangle(cornerRadius: 5, style: .continuous) 61 | } 62 | } 63 | } 64 | .opacity(getContentOpacity()) 65 | } 66 | .padding(EdgeInsets(top: 14.0, leading: 14.0, bottom: 12.0, trailing: 18.0)) 67 | .background( 68 | GeometryReader { 69 | Color.clear.preference( 70 | key: PushHeightPreferenceKey.self, 71 | value: $0.size.height 72 | ) 73 | } 74 | ) 75 | .onPreferenceChange(PushHeightPreferenceKey.self) { 76 | pushHeight = $0 77 | } 78 | .background(.thinMaterial) 79 | .background( 80 | getPushBackground(), 81 | in: RoundedRectangle(cornerRadius: 24.0, style: .continuous) 82 | ) 83 | .animation(.linear(duration: 0.2), value: notStack) 84 | } 85 | 86 | private func getPushBackground() -> some ShapeStyle { 87 | if !notStack { 88 | if index == 0 { 89 | return .quaternary 90 | } 91 | 92 | if index == 1 { 93 | return .tertiary 94 | } 95 | 96 | return .secondary 97 | } 98 | 99 | return .quaternary 100 | } 101 | 102 | private func getContentOpacity() -> Double { 103 | if !notStack && index > 2 { 104 | return 0.0 105 | } 106 | 107 | return 1.0 108 | } 109 | } 110 | 111 | extension NotificationPushView { 112 | struct PushHeightPreferenceKey: PreferenceKey { 113 | /// The default value of the preference. 114 | /// 115 | /// Views that have no explicit value for the key produce this default 116 | /// value. Combining child views may remove an implicit value produced by 117 | /// using the default. This means that `reduce(value: &x, nextValue: 118 | /// {defaultValue})` shouldn't change the meaning of `x`. 119 | static var defaultValue: CGFloat = 0 120 | 121 | /// Combines a sequence of values by modifying the previously-accumulated 122 | /// value with the result of a closure that provides the next value. 123 | /// 124 | /// This method receives its values in view-tree order. Conceptually, this 125 | /// combines the preference value from one tree with that of its next 126 | /// sibling. 127 | /// 128 | /// - Parameters: 129 | /// - value: The value accumulated through previous calls to this method. 130 | /// The implementation should modify this value. 131 | /// - nextValue: A closure that returns the next value in the sequence. 132 | static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { 133 | value = max(value, nextValue()) 134 | } 135 | } 136 | 137 | struct ContentPreferenceKey: PreferenceKey { 138 | static var defaultValue: CGFloat = 0 139 | 140 | static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { 141 | value = max(value, nextValue()) 142 | } 143 | } 144 | } 145 | 146 | //struct NotificationPushView_Previews: PreviewProvider { 147 | // @State private var toggleState = false 148 | // 149 | // static var previews: some View { 150 | // NotificationPushView( 151 | // notStack: $toggleState, 152 | // model: .init( 153 | // icon: .init(named: "notification-push-app-icon"), 154 | // userImage: .init(named: "notification-push-janum"), 155 | // contentImage: .init(named: "notification-content-image-3"), 156 | // title: "Janum Trivedi", 157 | // subtitle: "Can we talk\nabout the game\nlast night?", 158 | // time: "2h ago" 159 | // ), 160 | // index: 0 161 | // ) 162 | // .padding(.horizontal, 24) 163 | // } 164 | //} 165 | -------------------------------------------------------------------------------- /UIExamples/Modules/Notifications/View1/NotificationSetupView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct NotificationSetupView: View { 4 | @Environment(\.dismiss) private var dismiss 5 | @State private var animate = false 6 | 7 | let blurHeight = 160.0 8 | let blurTint: UIColor = .secondarySystemBackground.withAlphaComponent(0) 9 | let center = UNUserNotificationCenter.current() 10 | let impactFeedback = UIImpactFeedbackGenerator(style: .soft) 11 | let model = NotificationSetupModel.initial 12 | 13 | var body: some View { 14 | GeometryReader { geometry in 15 | ZStack(alignment: .bottom) { 16 | ScrollView(.vertical, showsIndicators: false) { 17 | VStack { 18 | VStack(spacing: 16) { 19 | Image(systemName: "app.badge") 20 | // .symbolEffect(.bounce.up.byLayer, value: animate) 21 | .frame(width: 66, height: 66) 22 | .font(.system(size: 56, weight: .semibold)) 23 | .foregroundColor(.red) 24 | .fontWeight(.semibold) 25 | 26 | Text("Let's set up your notifications.") 27 | .font(.system(size: 34, weight: .bold)) 28 | .multilineTextAlignment(.center) 29 | .kerning(-0.22) 30 | .padding(.horizontal, 40) 31 | .padding(.top, 6) 32 | 33 | Text("You can modify and turn off individual notifications at any time in Settings.") 34 | .font(.system(size: 16)) 35 | .multilineTextAlignment(.center) 36 | .lineSpacing(2) 37 | .padding(.horizontal, 40) 38 | } 39 | .offset(y: -12) 40 | 41 | VStack(alignment: .leading, spacing: 32) { 42 | ForEach(model.notificationFeatures, id: \.self) { item in 43 | HStack(spacing: 10) { 44 | Image(systemName: item.systemNamed) 45 | .font(.system(size: 36, weight: .semibold)) 46 | .frame(width: 52, height: 52) 47 | .foregroundColor(.secondary) 48 | .fontWeight(.semibold) 49 | .padding(.leading, 24) 50 | 51 | VStack(alignment: .leading, spacing: 2) { 52 | Text(item.title) 53 | .font(.system(size: 15, weight: .semibold)) 54 | 55 | Text(item.subtitle) 56 | .font(.system(size: 15, weight: .regular)) 57 | .foregroundColor(.secondary) 58 | .multilineTextAlignment(.leading) 59 | } 60 | .padding(.trailing, 32) 61 | } 62 | } 63 | } 64 | .offset(y: 18) 65 | } 66 | .padding() 67 | .padding(.top, geometry.size.height/12 + geometry.safeAreaInsets.top) 68 | .padding(.bottom, geometry.safeAreaInsets.bottom + blurHeight) 69 | } 70 | .onAppear { 71 | animate.toggle() 72 | } 73 | .onTapGesture { 74 | animate.toggle() 75 | } 76 | 77 | ZStack(alignment: .bottom) { 78 | BlurView(colorTint: blurTint) 79 | .mask({ 80 | LinearGradient( 81 | gradient: Gradient(colors: [.clear, .black]), 82 | startPoint: UnitPoint(x: 0, y: 0.1), 83 | endPoint: UnitPoint(x: 0, y: 0.4) 84 | ) 85 | }) 86 | .frame(height: blurHeight + geometry.safeAreaInsets.bottom) 87 | 88 | VStack(spacing: 4) { 89 | Button(action: { 90 | impactFeedback.impactOccurred() 91 | center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in 92 | if let error = error { 93 | // Handle the error here. 94 | } 95 | 96 | dismiss() 97 | } 98 | }) { 99 | Text("Turn on Notifications") 100 | .foregroundColor(.white) 101 | .frame(height: 24) 102 | } 103 | .buttonStyle(NiceButtonStyle(color: .blue)) 104 | .padding(.horizontal, 44) 105 | 106 | Button { 107 | dismiss() 108 | } label: { 109 | Text("Not Now") 110 | .foregroundColor(.blue) 111 | .fontWeight(.semibold) 112 | } 113 | .frame(height: 50) 114 | } 115 | .padding(.bottom, geometry.safeAreaInsets.bottom) 116 | } 117 | } 118 | .edgesIgnoringSafeArea(.all) 119 | } 120 | } 121 | } 122 | 123 | struct NotificationSetupView_Previews: PreviewProvider { 124 | static var previews: some View { 125 | NotificationSetupView() 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /UIExamples/Modules/Notifications/View2/NotificationSetupView2.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct NotificationSetupView2: View { 4 | @Environment(\.dismiss) private var dismiss 5 | @State var animate: Bool = false 6 | 7 | let blurHeight = 66.0 8 | let blurTint: UIColor = .secondarySystemBackground.withAlphaComponent(0) 9 | let center = UNUserNotificationCenter.current() 10 | let impactFeedback = UIImpactFeedbackGenerator(style: .soft) 11 | let model = NotificationSetupModel.initial 12 | 13 | var body: some View { 14 | ZStack(alignment: .top) { 15 | VStack { 16 | Spacer(minLength: 0) 17 | 18 | ScrollView(showsIndicators: false) { 19 | VStack(spacing: animate ? 10 : -100) { 20 | ForEach(model.notificationPushes, id: \.self) { model in 21 | NotificationPushView(notStack: $animate, model: model, index: 0) 22 | .rotationEffect(Angle(degrees: 180)).scaleEffect(x: -1.0, y: 1.0, anchor: .center) 23 | } 24 | } 25 | .animation(.spring().speed(0.4), value: animate) 26 | } 27 | .scrollDisabled(true) 28 | .rotationEffect(Angle(degrees: 180)).scaleEffect(x: -1.0, y: 1.0, anchor: .center) 29 | .onAppear { 30 | animate.toggle() 31 | } 32 | 33 | VStack(spacing: 18) { 34 | Text("Let's set up your notifications.") 35 | .font(.system(size: 34, weight: .bold)) 36 | .multilineTextAlignment(.center) 37 | .kerning(-0.22) 38 | .padding(.top, 22) 39 | 40 | Text("You can modify and turn off individual notifications at any time in Settings.") 41 | .font(.system(size: 16)) 42 | .multilineTextAlignment(.center) 43 | .lineSpacing(2) 44 | } 45 | 46 | Spacer() 47 | 48 | VStack(spacing: 4) { 49 | Button(action: { 50 | impactFeedback.impactOccurred() 51 | 52 | Task { 53 | _ = try? await center.requestAuthorization(options: [.alert, .sound, .badge]) 54 | dismiss() 55 | } 56 | }) { 57 | Text("Turn on Notifications") 58 | .foregroundColor(.white) 59 | .frame(height: 24) 60 | } 61 | .buttonStyle(NiceButtonStyle(color: .blue)) 62 | 63 | Button { 64 | dismiss() 65 | } label: { 66 | Text("Not Now") 67 | .foregroundColor(.blue) 68 | .fontWeight(.semibold) 69 | } 70 | .frame(height: 50) 71 | .padding(.bottom, 6) 72 | } 73 | .padding(.top, 24) 74 | } 75 | .padding(.horizontal, 44) 76 | 77 | BlurView(colorTint: blurTint) 78 | .mask { 79 | LinearGradient( 80 | gradient: Gradient(colors: [.clear, .black]), 81 | startPoint: UnitPoint(x: 0, y: 1), 82 | endPoint: UnitPoint(x: 0, y: 0) 83 | ) 84 | } 85 | .frame(height: blurHeight) 86 | } 87 | .edgesIgnoringSafeArea(.top) 88 | } 89 | } 90 | 91 | struct NotificationSetupView2_Previews: PreviewProvider { 92 | static var previews: some View { 93 | NotificationSetupView2() 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /UIExamples/Modules/Notifications/View3/NotificationSetupView3.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct NotificationSetupView3: View { 4 | @Environment(\.dismiss) private var dismiss 5 | @State private var animate: Bool = false 6 | @State private var featuresHeight: CGFloat = 0 7 | @State private var pushesHeight: CGFloat = 0 8 | @State private var stopUpdateHeight: Bool = false 9 | @State private var pushesRowHeight: [CGFloat] = [] 10 | 11 | let blurHeight = 66.0 12 | let blurTint: UIColor = .secondarySystemBackground.withAlphaComponent(0) 13 | let center = UNUserNotificationCenter.current() 14 | let impactFeedback = UIImpactFeedbackGenerator(style: .soft) 15 | let model = NotificationSetupModel.initial 16 | let contentOffset: CGFloat = 0 17 | 18 | var body: some View { 19 | GeometryReader { geometry in 20 | ZStack(alignment: .top) { 21 | VStack { 22 | Spacer(minLength: 0) 23 | 24 | GeometryReader { contentGeometry in 25 | ZStack(alignment: .top) { 26 | VStack(spacing: 26) { 27 | VStack(spacing: 12) { 28 | Text("Get Notifications") 29 | .font(.system(size: 34, weight: .bold)) 30 | .multilineTextAlignment(.center) 31 | .kerning(-0.22) 32 | .padding(.top, 22) 33 | 34 | Text("You can modify and turn off individual notifications at any time in Settings.") 35 | .font(.system(size: 16)) 36 | .multilineTextAlignment(.center) 37 | .lineSpacing(2) 38 | } 39 | .padding(.horizontal, 44) 40 | 41 | VStack(alignment: .leading, spacing: 20) { 42 | ForEach(model.notificationFeatures, id: \.self) { item in 43 | HStack(spacing: 8) { 44 | Spacer() 45 | 46 | Image(systemName: item.systemNamed) 47 | .font(.system(size: 16, weight: .semibold)) 48 | .foregroundColor(.blue) 49 | .fontWeight(.semibold) 50 | 51 | VStack(alignment: .center, spacing: 2) { 52 | Text(item.title) 53 | .font(.system(size: 15, weight: .semibold)) 54 | } 55 | 56 | Spacer() 57 | } 58 | } 59 | } 60 | } 61 | .background( 62 | GeometryReader { geometry in 63 | Color.clear.preference( 64 | key: FeaturesPreferenceKey.self, 65 | value: geometry.size.height 66 | ) 67 | } 68 | ) 69 | .offset(y: (contentGeometry.size.height - featuresHeight - pushesHeight)/2 + pushesHeight + contentOffset + geometry.safeAreaInsets.top) 70 | 71 | ScrollView(showsIndicators: false) { 72 | VStack(spacing: animate ? 10 : -18) { 73 | ForEach(Array(model.notificationPushes.enumerated()), id: \.offset) { index, model in 74 | NotificationPushView(notStack: $animate, model: model, index: index) 75 | .padding(.bottom, calculatePaddingForPush(at: index)) 76 | .scaleEffect(calculateScaleForPush(at: index)) 77 | .zIndex(Double(self.model.notificationPushes.count - index)) 78 | .rotationEffect(Angle(degrees: 180)).scaleEffect(x: -1.0, y: 1.0, anchor: .center) 79 | .background( 80 | GeometryReader { geometry in 81 | Color.clear.preference( 82 | key: PushViewPreferenceKey.self, 83 | value: geometry.size.height 84 | ) 85 | } 86 | ) 87 | .onPreferenceChange(PushViewPreferenceKey.self) { 88 | pushesRowHeight.append($0) 89 | } 90 | } 91 | } 92 | .padding(.horizontal, 24) 93 | .background( 94 | GeometryReader { geometry in 95 | Color.clear.preference( 96 | key: PushesPreferenceKey.self, 97 | value: geometry.size.height 98 | ) 99 | } 100 | ) 101 | } 102 | .scrollDisabled(true) 103 | .rotationEffect(Angle(degrees: 180)).scaleEffect(x: -1.0, y: 1.0, anchor: .center) 104 | .onTapGesture { 105 | stopUpdateHeight = true 106 | animate.toggle() 107 | } 108 | .offset(y: -contentGeometry.size.height + pushesHeight + (contentGeometry.size.height - featuresHeight - pushesHeight)/2 + contentOffset + geometry.safeAreaInsets.top) 109 | } 110 | .onPreferenceChange(FeaturesPreferenceKey.self) { 111 | featuresHeight = $0 112 | } 113 | .onPreferenceChange(PushesPreferenceKey.self) { 114 | if !stopUpdateHeight { 115 | pushesHeight = $0 116 | } 117 | } 118 | } 119 | 120 | Spacer(minLength: 0) 121 | 122 | VStack(spacing: 4) { 123 | Button(action: { 124 | impactFeedback.impactOccurred() 125 | 126 | Task { 127 | _ = try? await center.requestAuthorization(options: [.alert, .sound, .badge]) 128 | dismiss() 129 | } 130 | }) { 131 | Text("Turn on Notifications") 132 | .foregroundColor(.white) 133 | .frame(height: 24) 134 | } 135 | .buttonStyle(NiceButtonStyle(color: .blue)) 136 | 137 | Button { 138 | dismiss() 139 | } label: { 140 | Text("Not Now") 141 | .foregroundColor(.blue) 142 | .fontWeight(.semibold) 143 | } 144 | .frame(height: 50) 145 | .padding(.bottom, 6) 146 | } 147 | .padding(.top, 24) 148 | .padding(.horizontal, 44) 149 | } 150 | 151 | BlurView(colorTint: blurTint) 152 | .mask { 153 | LinearGradient( 154 | gradient: Gradient(colors: [.clear, .black]), 155 | startPoint: UnitPoint(x: 0, y: 1), 156 | endPoint: UnitPoint(x: 0, y: 0) 157 | ) 158 | } 159 | .frame(height: blurHeight) 160 | } 161 | .animation(.spring().speed(0.7), value: animate) 162 | .edgesIgnoringSafeArea(.top) 163 | } 164 | } 165 | 166 | private func calculatePaddingForPush(at index: Int) -> CGFloat { 167 | guard !animate, index != 0, pushesRowHeight.count > index else { return 0.0 } 168 | 169 | let scale = 1.0 - CGFloat(index) / 10.0 170 | let scaledHeight = pushesRowHeight[index] * scale 171 | let heightDifference = pushesRowHeight[index] - scaledHeight 172 | 173 | if index > 2 { 174 | return -scaledHeight - heightDifference/2 175 | } 176 | 177 | return -abs(pushesRowHeight[0] - pushesRowHeight[index] + heightDifference/2) 178 | } 179 | 180 | private func calculateScaleForPush(at index: Int) -> CGSize { 181 | guard !animate else { return CGSize(width: 1, height: 1) } 182 | 183 | let scale = animate ? 1 : 1.0 - CGFloat(index) / 10.0 184 | let size = CGSize(width: scale, height: scale) 185 | 186 | return size 187 | } 188 | } 189 | 190 | extension NotificationSetupView3 { 191 | struct FeaturesPreferenceKey: PreferenceKey { 192 | static let defaultValue: CGFloat = 0 193 | 194 | static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { 195 | value = max(value, nextValue()) 196 | } 197 | } 198 | 199 | struct PushesPreferenceKey: PreferenceKey { 200 | static let defaultValue: CGFloat = 0 201 | 202 | static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { 203 | value = max(value, nextValue()) 204 | } 205 | } 206 | 207 | struct PushViewPreferenceKey: PreferenceKey { 208 | static let defaultValue: CGFloat = 0 209 | 210 | static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { 211 | value = max(value, nextValue()) 212 | } 213 | } 214 | } 215 | 216 | struct NotificationSetupView3_Previews: PreviewProvider { 217 | static var previews: some View { 218 | NotificationSetupView3() 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /UIExamples/Shared/Extentions/UIColor+Extentions.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | /// get a gradient color 4 | extension UIColor { 5 | func offset(by offset: CGFloat) -> UIColor { 6 | let (h, s, b, a) = hsba 7 | var newHue = h - offset 8 | 9 | /// make it go back to positive 10 | while newHue <= 0 { 11 | newHue += 1 12 | } 13 | let normalizedHue = newHue.truncatingRemainder(dividingBy: 1) 14 | return UIColor(hue: normalizedHue, saturation: s, brightness: b, alpha: a) 15 | } 16 | 17 | var hsba: (h: CGFloat, s: CGFloat, b: CGFloat, a: CGFloat) { 18 | var h: CGFloat = 0, s: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0 19 | self.getHue(&h, saturation: &s, brightness: &b, alpha: &a) 20 | return (h: h, s: s, b: b, a: a) 21 | } 22 | 23 | /** 24 | Create a UIColor from a hex code. 25 | 26 | Example: 27 | 28 | let color = UIColor(hex: 0x00aeef) 29 | */ 30 | convenience init(hex: UInt, alpha: CGFloat = 1) { 31 | self.init( 32 | red: CGFloat((hex & 0xFF0000) >> 16) / 255.0, 33 | green: CGFloat((hex & 0x00FF00) >> 8) / 255.0, 34 | blue: CGFloat(hex & 0x0000FF) / 255.0, 35 | alpha: alpha 36 | ) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /UIExamples/Shared/Extentions/View+Builders.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | extension View { 4 | /// Applies the given transform if the given condition evaluates to `true`. 5 | /// - Parameters: 6 | /// - condition: The condition to evaluate. 7 | /// - transform: The transform to apply to the source `View`. 8 | /// - Returns: Either the original `View` or the modified `View` if the condition is `true`. 9 | @ViewBuilder func `if`(_ condition: Bool, transform: (Self) -> Content) -> some View { 10 | if condition { 11 | transform(self) 12 | } else { 13 | self 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /UIExamples/Shared/Models/NotificationFeature.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct NotificationFeature: Identifiable, Hashable { 4 | var id: String { 5 | return systemNamed + title 6 | } 7 | 8 | let systemNamed: String 9 | let title: String 10 | let subtitle: String 11 | } 12 | -------------------------------------------------------------------------------- /UIExamples/Shared/Models/NotificationPushModel.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | struct NotificationPushModel: Hashable { 4 | init(icon: UIImage? = nil, userImage: UIImage? = nil, contentImage: UIImage? = nil, title: String = "Title", subtitle: String = "Description", time: String = "now") { 5 | self.icon = icon ?? UIImage(named: "notification-push-unknown")! 6 | self.userImage = userImage 7 | self.contentImage = contentImage 8 | self.title = title 9 | self.subtitle = subtitle 10 | self.time = time 11 | } 12 | 13 | let icon: UIImage 14 | let userImage: UIImage? 15 | let contentImage: UIImage? 16 | let title: String 17 | let subtitle: String 18 | let time: String 19 | } 20 | -------------------------------------------------------------------------------- /UIExamples/Shared/Models/SwiftUIExample.swift: -------------------------------------------------------------------------------- 1 | //import UIKit 2 | import SwiftUI 3 | 4 | struct SwiftUIExample { 5 | let title: String 6 | let background: UIColor 7 | let sfSymbol: String 8 | var view: AnyView 9 | 10 | init(title: String, background: UIColor, sfSymbol: String, view: any View) { 11 | self.title = title 12 | self.background = background 13 | self.sfSymbol = sfSymbol 14 | self.view = AnyView(view) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /UIExamples/Shared/Resources/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /UIExamples/Shared/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /UIExamples/Shared/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /UIExamples/Shared/Resources/Assets.xcassets/app-photos.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "app-photos.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "app-photos@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "app-photos@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /UIExamples/Shared/Resources/Assets.xcassets/app-photos.imageset/app-photos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UIXaker/UIExamples/e482ff942f9e3a0fd4b3368c209bfa2be28141e5/UIExamples/Shared/Resources/Assets.xcassets/app-photos.imageset/app-photos.png -------------------------------------------------------------------------------- /UIExamples/Shared/Resources/Assets.xcassets/app-photos.imageset/app-photos@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UIXaker/UIExamples/e482ff942f9e3a0fd4b3368c209bfa2be28141e5/UIExamples/Shared/Resources/Assets.xcassets/app-photos.imageset/app-photos@2x.png -------------------------------------------------------------------------------- /UIExamples/Shared/Resources/Assets.xcassets/app-photos.imageset/app-photos@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UIXaker/UIExamples/e482ff942f9e3a0fd4b3368c209bfa2be28141e5/UIExamples/Shared/Resources/Assets.xcassets/app-photos.imageset/app-photos@3x.png -------------------------------------------------------------------------------- /UIExamples/Shared/Resources/Assets.xcassets/notification-content-image-2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "notification-content-image-2.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /UIExamples/Shared/Resources/Assets.xcassets/notification-content-image-2.imageset/notification-content-image-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UIXaker/UIExamples/e482ff942f9e3a0fd4b3368c209bfa2be28141e5/UIExamples/Shared/Resources/Assets.xcassets/notification-content-image-2.imageset/notification-content-image-2.png -------------------------------------------------------------------------------- /UIExamples/Shared/Resources/Assets.xcassets/notification-content-image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "notification-content-image.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /UIExamples/Shared/Resources/Assets.xcassets/notification-content-image.imageset/notification-content-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UIXaker/UIExamples/e482ff942f9e3a0fd4b3368c209bfa2be28141e5/UIExamples/Shared/Resources/Assets.xcassets/notification-content-image.imageset/notification-content-image.png -------------------------------------------------------------------------------- /UIExamples/Shared/Resources/Assets.xcassets/notification-push-app-icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "notification-push-app-icon.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /UIExamples/Shared/Resources/Assets.xcassets/notification-push-app-icon.imageset/notification-push-app-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UIXaker/UIExamples/e482ff942f9e3a0fd4b3368c209bfa2be28141e5/UIExamples/Shared/Resources/Assets.xcassets/notification-push-app-icon.imageset/notification-push-app-icon.png -------------------------------------------------------------------------------- /UIExamples/Shared/Resources/Assets.xcassets/notification-push-unknown.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "No App Icon.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "No App Icon@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "No App Icon@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /UIExamples/Shared/Resources/Assets.xcassets/notification-push-unknown.imageset/No App Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UIXaker/UIExamples/e482ff942f9e3a0fd4b3368c209bfa2be28141e5/UIExamples/Shared/Resources/Assets.xcassets/notification-push-unknown.imageset/No App Icon.png -------------------------------------------------------------------------------- /UIExamples/Shared/Resources/Assets.xcassets/notification-push-unknown.imageset/No App Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UIXaker/UIExamples/e482ff942f9e3a0fd4b3368c209bfa2be28141e5/UIExamples/Shared/Resources/Assets.xcassets/notification-push-unknown.imageset/No App Icon@2x.png -------------------------------------------------------------------------------- /UIExamples/Shared/Resources/Assets.xcassets/notification-push-unknown.imageset/No App Icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UIXaker/UIExamples/e482ff942f9e3a0fd4b3368c209bfa2be28141e5/UIExamples/Shared/Resources/Assets.xcassets/notification-push-unknown.imageset/No App Icon@3x.png -------------------------------------------------------------------------------- /UIExamples/Shared/Resources/Assets.xcassets/notification-push-user.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "notification-push-user.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /UIExamples/Shared/Resources/Assets.xcassets/notification-push-user.imageset/notification-push-user.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UIXaker/UIExamples/e482ff942f9e3a0fd4b3368c209bfa2be28141e5/UIExamples/Shared/Resources/Assets.xcassets/notification-push-user.imageset/notification-push-user.jpg -------------------------------------------------------------------------------- /UIExamples/Shared/Resources/Strings/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "access-restricted_title" = "Access Required"; 2 | "access-restricted_step-1" = "Tap the button at the bottom of this screen to open Settings"; 3 | "access-restricted_step-2" = "Once in the Settings, tap on 'Photos'"; 4 | "access-restricted_step-3" = "Select ‘All Photos’"; 5 | "access-restricted_step-3-ios17" = "Select ’Full Access’"; 6 | "access-restricted_step-4" = "Re-open the app"; 7 | "access-restricted_btn" = "Open Settings"; 8 | "access-restricted_settings_app-photos" = "Photos"; 9 | "access-restricted_settings_app-photos_none" = "None"; 10 | "access-restricted_settings_tableview-group_title" = "Allow Photos Access"; 11 | "access-restricted_settings_tableview-group_selected" = "Selected Photos"; 12 | "access-restricted_settings_tableview-group_all" = "All Photos"; 13 | "access-restricted_settings_tableview-group_title-ios17" = "Photo Library Access"; 14 | "access-restricted_settings_tableview-group_selected-ios17" = "Limited Access"; 15 | "access-restricted_settings_tableview-group_selected-ios17_subtitle" = "Selected photos and add access"; 16 | "access-restricted_settings_tableview-group_all-ios17" = "Full Access"; 17 | "access-restricted_settings_tableview-group_all-ios17_subtitle" = "Photos and Videos"; 18 | "access-restricted_settings_tableview-group_none" = "None"; 19 | -------------------------------------------------------------------------------- /UIExamples/Shared/Views/CircleSmallButton.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct CircleSmallButton: ButtonStyle { 4 | @State var icon: String 5 | private let blurTint: UIColor = .secondarySystemBackground.withAlphaComponent(0) 6 | 7 | func makeBody(configuration: Configuration) -> some View { 8 | ZStack { 9 | Circle() 10 | .foregroundColor(.secondary.opacity(0.4)) 11 | .overlay( 12 | configuration.isPressed ? Color.black.opacity(0.15) : Color.clear 13 | ) .clipShape(Circle()) 14 | .frame(width: 30, height: 30) 15 | 16 | BlurView(colorTint: blurTint) 17 | .clipShape(Circle()) 18 | .frame(width: 30, height: 30) 19 | 20 | Image(systemName: icon) 21 | .font(.system(size: 16, weight: .semibold)) 22 | .foregroundColor(.primary) 23 | } 24 | .scaleEffect(configuration.isPressed ? 0.95 : 1.0) 25 | .frame(width: 64, height: 64) 26 | .contentShape(Rectangle()) 27 | .animation(.spring().speed(1.5)) 28 | } 29 | } 30 | 31 | struct BlurView: UIViewRepresentable { 32 | @Environment(\.colorScheme) var colorScheme 33 | var style: UIBlurEffect.Style? 34 | var colorTint: UIColor? 35 | 36 | private var blurStyle: UIBlurEffect.Style { 37 | if let blur = style { 38 | return blur 39 | } 40 | return colorScheme == .dark ? .dark : .light 41 | } 42 | 43 | func makeUIView(context: Context) -> UIVisualEffectView { 44 | let view = UIVisualEffectView(effect: UIBlurEffect(style: blurStyle)) 45 | 46 | return view 47 | } 48 | 49 | func updateUIView(_ uiView: UIVisualEffectView, context: Context) { 50 | uiView.effect = UIBlurEffect(style: blurStyle) 51 | 52 | if let colorTint = colorTint { 53 | uiView.ios14_colorTint = colorTint 54 | } 55 | } 56 | } 57 | 58 | struct CircleSmallButton_Previews: PreviewProvider { 59 | static var previews: some View { 60 | Button { } label: { } 61 | .buttonStyle(CircleSmallButton(icon: "xmark")) 62 | } 63 | } 64 | 65 | 66 | extension UIVisualEffectView { 67 | var ios14_blurRadius: CGFloat { 68 | get { 69 | return gaussianBlur?.requestedValues?["inputRadius"] as? CGFloat ?? 0 70 | } 71 | set { 72 | prepareForChanges() 73 | gaussianBlur?.requestedValues?["inputRadius"] = newValue 74 | applyChanges() 75 | } 76 | } 77 | var ios14_colorTint: UIColor? { 78 | get { 79 | return sourceOver?.value(forKeyPath: "color") as? UIColor 80 | } 81 | set { 82 | prepareForChanges() 83 | sourceOver?.setValue(newValue, forKeyPath: "color") 84 | sourceOver?.perform(Selector(("applyRequestedEffectToView:")), with: overlayView) 85 | applyChanges() 86 | overlayView?.backgroundColor = newValue 87 | } 88 | } 89 | } 90 | 91 | private extension UIVisualEffectView { 92 | var backdropView: UIView? { 93 | return subview(of: NSClassFromString("_UIVisualEffectBackdropView")) 94 | } 95 | var overlayView: UIView? { 96 | return subview(of: NSClassFromString("_UIVisualEffectSubview")) 97 | } 98 | var gaussianBlur: NSObject? { 99 | return backdropView?.value(forKey: "filters", withFilterType: "gaussianBlur") 100 | } 101 | var sourceOver: NSObject? { 102 | return overlayView?.value(forKey: "viewEffects", withFilterType: "sourceOver") 103 | } 104 | func prepareForChanges() { 105 | self.effect = UIBlurEffect(style: .light) 106 | gaussianBlur?.setValue(1.0, forKeyPath: "requestedScaleHint") 107 | } 108 | func applyChanges() { 109 | backdropView?.perform(Selector(("applyRequestedFilterEffects"))) 110 | } 111 | } 112 | 113 | private extension NSObject { 114 | var requestedValues: [String: Any]? { 115 | get { return value(forKeyPath: "requestedValues") as? [String: Any] } 116 | set { setValue(newValue, forKeyPath: "requestedValues") } 117 | } 118 | func value(forKey key: String, withFilterType filterType: String) -> NSObject? { 119 | return (value(forKeyPath: key) as? [NSObject])?.first { $0.value(forKeyPath: "filterType") as? String == filterType } 120 | } 121 | } 122 | 123 | private extension UIView { 124 | func subview(of classType: AnyClass?) -> UIView? { 125 | return subviews.first { type(of: $0) == classType } 126 | } 127 | } 128 | --------------------------------------------------------------------------------