├── .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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------