├── .github
└── workflows
│ └── actions.yml
├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── Example
├── TextFieldAlertExample.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── TextFieldAlertExample
│ ├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
│ ├── ExampleView.swift
│ ├── ExampleViewModel.swift
│ ├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
│ └── TextFieldAlertExampleApp.swift
├── LICENSE
├── Package.swift
├── README.md
├── Resources
└── TextFieldAlert.gif
└── Sources
└── TextFieldAlert
├── Extensions.swift
├── TextFieldAlert.swift
├── TextFieldAlertViewController.swift
└── TextFieldWrapper.swift
/.github/workflows/actions.yml:
--------------------------------------------------------------------------------
1 | name: TextFieldAlert
2 |
3 | on:
4 | push:
5 | branches: [ "main" ]
6 | pull_request:
7 | branches: [ "main" ]
8 |
9 | jobs:
10 | build-iOS:
11 |
12 | runs-on: macos-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v3
16 | - name: Build Package
17 | run: xcrun xcodebuild build -scheme 'TextFieldAlert' -destination 'generic/platform=ios'
18 | - name: Build Example
19 | run: xcrun xcodebuild build -project 'Example/TextFieldAlertExample.xcodeproj' -scheme 'TextFieldAlertExample' -destination 'generic/platform=iOS Simulator'
20 |
21 | build-tvOS:
22 |
23 | runs-on: macos-latest
24 |
25 | steps:
26 | - uses: actions/checkout@v3
27 | - name: Build Package
28 | run: xcrun xcodebuild build -scheme 'TextFieldAlert' -destination 'generic/platform=tvos'
29 | - name: Build Example
30 | run: xcrun xcodebuild build -project 'Example/TextFieldAlertExample.xcodeproj' -scheme 'TextFieldAlertExample' -destination 'generic/platform=tvOS Simulator'
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/TextFieldAlertExample.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 55;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | AEA31F11286666B300779C07 /* TextFieldAlertExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEA31F10286666B300779C07 /* TextFieldAlertExampleApp.swift */; };
11 | AEA31F13286666B300779C07 /* ExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEA31F12286666B300779C07 /* ExampleView.swift */; };
12 | AEA31F15286666B400779C07 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AEA31F14286666B400779C07 /* Assets.xcassets */; };
13 | AEA31F18286666B400779C07 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AEA31F17286666B400779C07 /* Preview Assets.xcassets */; };
14 | AEA31F212866672400779C07 /* ExampleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEA31F202866672400779C07 /* ExampleViewModel.swift */; };
15 | AEA31F25286668E800779C07 /* TextFieldAlert in Frameworks */ = {isa = PBXBuildFile; productRef = AEA31F24286668E800779C07 /* TextFieldAlert */; };
16 | /* End PBXBuildFile section */
17 |
18 | /* Begin PBXFileReference section */
19 | AEA31F0D286666B300779C07 /* TextFieldAlertExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TextFieldAlertExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
20 | AEA31F10286666B300779C07 /* TextFieldAlertExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldAlertExampleApp.swift; sourceTree = ""; };
21 | AEA31F12286666B300779C07 /* ExampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleView.swift; sourceTree = ""; };
22 | AEA31F14286666B400779C07 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
23 | AEA31F17286666B400779C07 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
24 | AEA31F1F286666E000779C07 /* TextFieldAlert */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = TextFieldAlert; path = ..; sourceTree = ""; };
25 | AEA31F202866672400779C07 /* ExampleViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleViewModel.swift; sourceTree = ""; };
26 | /* End PBXFileReference section */
27 |
28 | /* Begin PBXFrameworksBuildPhase section */
29 | AEA31F0A286666B300779C07 /* Frameworks */ = {
30 | isa = PBXFrameworksBuildPhase;
31 | buildActionMask = 2147483647;
32 | files = (
33 | AEA31F25286668E800779C07 /* TextFieldAlert in Frameworks */,
34 | );
35 | runOnlyForDeploymentPostprocessing = 0;
36 | };
37 | /* End PBXFrameworksBuildPhase section */
38 |
39 | /* Begin PBXGroup section */
40 | AEA31F04286666B300779C07 = {
41 | isa = PBXGroup;
42 | children = (
43 | AEA31F1E286666E000779C07 /* Packages */,
44 | AEA31F0F286666B300779C07 /* TextFieldAlertExample */,
45 | AEA31F0E286666B300779C07 /* Products */,
46 | AEA31F23286668E800779C07 /* Frameworks */,
47 | );
48 | sourceTree = "";
49 | };
50 | AEA31F0E286666B300779C07 /* Products */ = {
51 | isa = PBXGroup;
52 | children = (
53 | AEA31F0D286666B300779C07 /* TextFieldAlertExample.app */,
54 | );
55 | name = Products;
56 | sourceTree = "";
57 | };
58 | AEA31F0F286666B300779C07 /* TextFieldAlertExample */ = {
59 | isa = PBXGroup;
60 | children = (
61 | AEA31F10286666B300779C07 /* TextFieldAlertExampleApp.swift */,
62 | AEA31F12286666B300779C07 /* ExampleView.swift */,
63 | AEA31F202866672400779C07 /* ExampleViewModel.swift */,
64 | AEA31F14286666B400779C07 /* Assets.xcassets */,
65 | AEA31F16286666B400779C07 /* Preview Content */,
66 | );
67 | path = TextFieldAlertExample;
68 | sourceTree = "";
69 | };
70 | AEA31F16286666B400779C07 /* Preview Content */ = {
71 | isa = PBXGroup;
72 | children = (
73 | AEA31F17286666B400779C07 /* Preview Assets.xcassets */,
74 | );
75 | path = "Preview Content";
76 | sourceTree = "";
77 | };
78 | AEA31F1E286666E000779C07 /* Packages */ = {
79 | isa = PBXGroup;
80 | children = (
81 | AEA31F1F286666E000779C07 /* TextFieldAlert */,
82 | );
83 | name = Packages;
84 | sourceTree = "";
85 | };
86 | AEA31F23286668E800779C07 /* Frameworks */ = {
87 | isa = PBXGroup;
88 | children = (
89 | );
90 | name = Frameworks;
91 | sourceTree = "";
92 | };
93 | /* End PBXGroup section */
94 |
95 | /* Begin PBXNativeTarget section */
96 | AEA31F0C286666B300779C07 /* TextFieldAlertExample */ = {
97 | isa = PBXNativeTarget;
98 | buildConfigurationList = AEA31F1B286666B400779C07 /* Build configuration list for PBXNativeTarget "TextFieldAlertExample" */;
99 | buildPhases = (
100 | AEA31F09286666B300779C07 /* Sources */,
101 | AEA31F0A286666B300779C07 /* Frameworks */,
102 | AEA31F0B286666B300779C07 /* Resources */,
103 | );
104 | buildRules = (
105 | );
106 | dependencies = (
107 | );
108 | name = TextFieldAlertExample;
109 | packageProductDependencies = (
110 | AEA31F24286668E800779C07 /* TextFieldAlert */,
111 | );
112 | productName = TextFieldAlertExample;
113 | productReference = AEA31F0D286666B300779C07 /* TextFieldAlertExample.app */;
114 | productType = "com.apple.product-type.application";
115 | };
116 | /* End PBXNativeTarget section */
117 |
118 | /* Begin PBXProject section */
119 | AEA31F05286666B300779C07 /* Project object */ = {
120 | isa = PBXProject;
121 | attributes = {
122 | BuildIndependentTargetsInParallel = 1;
123 | LastSwiftUpdateCheck = 1340;
124 | LastUpgradeCheck = 1340;
125 | TargetAttributes = {
126 | AEA31F0C286666B300779C07 = {
127 | CreatedOnToolsVersion = 13.4.1;
128 | };
129 | };
130 | };
131 | buildConfigurationList = AEA31F08286666B300779C07 /* Build configuration list for PBXProject "TextFieldAlertExample" */;
132 | compatibilityVersion = "Xcode 13.0";
133 | developmentRegion = en;
134 | hasScannedForEncodings = 0;
135 | knownRegions = (
136 | en,
137 | Base,
138 | );
139 | mainGroup = AEA31F04286666B300779C07;
140 | productRefGroup = AEA31F0E286666B300779C07 /* Products */;
141 | projectDirPath = "";
142 | projectRoot = "";
143 | targets = (
144 | AEA31F0C286666B300779C07 /* TextFieldAlertExample */,
145 | );
146 | };
147 | /* End PBXProject section */
148 |
149 | /* Begin PBXResourcesBuildPhase section */
150 | AEA31F0B286666B300779C07 /* Resources */ = {
151 | isa = PBXResourcesBuildPhase;
152 | buildActionMask = 2147483647;
153 | files = (
154 | AEA31F18286666B400779C07 /* Preview Assets.xcassets in Resources */,
155 | AEA31F15286666B400779C07 /* Assets.xcassets in Resources */,
156 | );
157 | runOnlyForDeploymentPostprocessing = 0;
158 | };
159 | /* End PBXResourcesBuildPhase section */
160 |
161 | /* Begin PBXSourcesBuildPhase section */
162 | AEA31F09286666B300779C07 /* Sources */ = {
163 | isa = PBXSourcesBuildPhase;
164 | buildActionMask = 2147483647;
165 | files = (
166 | AEA31F13286666B300779C07 /* ExampleView.swift in Sources */,
167 | AEA31F212866672400779C07 /* ExampleViewModel.swift in Sources */,
168 | AEA31F11286666B300779C07 /* TextFieldAlertExampleApp.swift in Sources */,
169 | );
170 | runOnlyForDeploymentPostprocessing = 0;
171 | };
172 | /* End PBXSourcesBuildPhase section */
173 |
174 | /* Begin XCBuildConfiguration section */
175 | AEA31F19286666B400779C07 /* Debug */ = {
176 | isa = XCBuildConfiguration;
177 | buildSettings = {
178 | ALWAYS_SEARCH_USER_PATHS = NO;
179 | CLANG_ANALYZER_NONNULL = YES;
180 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
181 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
182 | CLANG_ENABLE_MODULES = YES;
183 | CLANG_ENABLE_OBJC_ARC = YES;
184 | CLANG_ENABLE_OBJC_WEAK = YES;
185 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
186 | CLANG_WARN_BOOL_CONVERSION = YES;
187 | CLANG_WARN_COMMA = YES;
188 | CLANG_WARN_CONSTANT_CONVERSION = YES;
189 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
190 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
191 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
192 | CLANG_WARN_EMPTY_BODY = YES;
193 | CLANG_WARN_ENUM_CONVERSION = YES;
194 | CLANG_WARN_INFINITE_RECURSION = YES;
195 | CLANG_WARN_INT_CONVERSION = YES;
196 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
197 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
198 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
199 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
200 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
201 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
202 | CLANG_WARN_STRICT_PROTOTYPES = YES;
203 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
204 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
205 | CLANG_WARN_UNREACHABLE_CODE = YES;
206 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
207 | COPY_PHASE_STRIP = NO;
208 | DEBUG_INFORMATION_FORMAT = dwarf;
209 | ENABLE_STRICT_OBJC_MSGSEND = YES;
210 | ENABLE_TESTABILITY = YES;
211 | GCC_C_LANGUAGE_STANDARD = gnu11;
212 | GCC_DYNAMIC_NO_PIC = NO;
213 | GCC_NO_COMMON_BLOCKS = YES;
214 | GCC_OPTIMIZATION_LEVEL = 0;
215 | GCC_PREPROCESSOR_DEFINITIONS = (
216 | "DEBUG=1",
217 | "$(inherited)",
218 | );
219 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
220 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
221 | GCC_WARN_UNDECLARED_SELECTOR = YES;
222 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
223 | GCC_WARN_UNUSED_FUNCTION = YES;
224 | GCC_WARN_UNUSED_VARIABLE = YES;
225 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
226 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
227 | MTL_FAST_MATH = YES;
228 | ONLY_ACTIVE_ARCH = YES;
229 | SDKROOT = iphoneos;
230 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
231 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
232 | TVOS_DEPLOYMENT_TARGET = 14.0;
233 | };
234 | name = Debug;
235 | };
236 | AEA31F1A286666B400779C07 /* Release */ = {
237 | isa = XCBuildConfiguration;
238 | buildSettings = {
239 | ALWAYS_SEARCH_USER_PATHS = NO;
240 | CLANG_ANALYZER_NONNULL = YES;
241 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
242 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
243 | CLANG_ENABLE_MODULES = YES;
244 | CLANG_ENABLE_OBJC_ARC = YES;
245 | CLANG_ENABLE_OBJC_WEAK = YES;
246 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
247 | CLANG_WARN_BOOL_CONVERSION = YES;
248 | CLANG_WARN_COMMA = YES;
249 | CLANG_WARN_CONSTANT_CONVERSION = YES;
250 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
251 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
252 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
253 | CLANG_WARN_EMPTY_BODY = YES;
254 | CLANG_WARN_ENUM_CONVERSION = YES;
255 | CLANG_WARN_INFINITE_RECURSION = YES;
256 | CLANG_WARN_INT_CONVERSION = YES;
257 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
258 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
259 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
260 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
261 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
262 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
263 | CLANG_WARN_STRICT_PROTOTYPES = YES;
264 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
265 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
266 | CLANG_WARN_UNREACHABLE_CODE = YES;
267 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
268 | COPY_PHASE_STRIP = NO;
269 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
270 | ENABLE_NS_ASSERTIONS = NO;
271 | ENABLE_STRICT_OBJC_MSGSEND = YES;
272 | GCC_C_LANGUAGE_STANDARD = gnu11;
273 | GCC_NO_COMMON_BLOCKS = YES;
274 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
275 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
276 | GCC_WARN_UNDECLARED_SELECTOR = YES;
277 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
278 | GCC_WARN_UNUSED_FUNCTION = YES;
279 | GCC_WARN_UNUSED_VARIABLE = YES;
280 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
281 | MTL_ENABLE_DEBUG_INFO = NO;
282 | MTL_FAST_MATH = YES;
283 | SDKROOT = iphoneos;
284 | SWIFT_COMPILATION_MODE = wholemodule;
285 | SWIFT_OPTIMIZATION_LEVEL = "-O";
286 | TVOS_DEPLOYMENT_TARGET = 14.0;
287 | VALIDATE_PRODUCT = YES;
288 | };
289 | name = Release;
290 | };
291 | AEA31F1C286666B400779C07 /* Debug */ = {
292 | isa = XCBuildConfiguration;
293 | buildSettings = {
294 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
295 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
296 | CODE_SIGN_STYLE = Automatic;
297 | CURRENT_PROJECT_VERSION = 1;
298 | DEVELOPMENT_ASSET_PATHS = "\"TextFieldAlertExample/Preview Content\"";
299 | DEVELOPMENT_TEAM = 8PM48474J4;
300 | ENABLE_PREVIEWS = YES;
301 | GENERATE_INFOPLIST_FILE = YES;
302 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
303 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
304 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
305 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
306 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
307 | LD_RUNPATH_SEARCH_PATHS = (
308 | "$(inherited)",
309 | "@executable_path/Frameworks",
310 | );
311 | MARKETING_VERSION = 1.0;
312 | PRODUCT_BUNDLE_IDENTIFIER = pl.sochalewski.TextFieldAlertExample;
313 | PRODUCT_NAME = "$(TARGET_NAME)";
314 | SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator";
315 | SUPPORTS_MACCATALYST = NO;
316 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
317 | SWIFT_EMIT_LOC_STRINGS = YES;
318 | SWIFT_VERSION = 5.0;
319 | TARGETED_DEVICE_FAMILY = "1,2,3";
320 | };
321 | name = Debug;
322 | };
323 | AEA31F1D286666B400779C07 /* Release */ = {
324 | isa = XCBuildConfiguration;
325 | buildSettings = {
326 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
327 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
328 | CODE_SIGN_STYLE = Automatic;
329 | CURRENT_PROJECT_VERSION = 1;
330 | DEVELOPMENT_ASSET_PATHS = "\"TextFieldAlertExample/Preview Content\"";
331 | DEVELOPMENT_TEAM = 8PM48474J4;
332 | ENABLE_PREVIEWS = YES;
333 | GENERATE_INFOPLIST_FILE = YES;
334 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
335 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
336 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
337 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
338 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
339 | LD_RUNPATH_SEARCH_PATHS = (
340 | "$(inherited)",
341 | "@executable_path/Frameworks",
342 | );
343 | MARKETING_VERSION = 1.0;
344 | PRODUCT_BUNDLE_IDENTIFIER = pl.sochalewski.TextFieldAlertExample;
345 | PRODUCT_NAME = "$(TARGET_NAME)";
346 | SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator";
347 | SUPPORTS_MACCATALYST = NO;
348 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
349 | SWIFT_EMIT_LOC_STRINGS = YES;
350 | SWIFT_VERSION = 5.0;
351 | TARGETED_DEVICE_FAMILY = "1,2,3";
352 | };
353 | name = Release;
354 | };
355 | /* End XCBuildConfiguration section */
356 |
357 | /* Begin XCConfigurationList section */
358 | AEA31F08286666B300779C07 /* Build configuration list for PBXProject "TextFieldAlertExample" */ = {
359 | isa = XCConfigurationList;
360 | buildConfigurations = (
361 | AEA31F19286666B400779C07 /* Debug */,
362 | AEA31F1A286666B400779C07 /* Release */,
363 | );
364 | defaultConfigurationIsVisible = 0;
365 | defaultConfigurationName = Release;
366 | };
367 | AEA31F1B286666B400779C07 /* Build configuration list for PBXNativeTarget "TextFieldAlertExample" */ = {
368 | isa = XCConfigurationList;
369 | buildConfigurations = (
370 | AEA31F1C286666B400779C07 /* Debug */,
371 | AEA31F1D286666B400779C07 /* Release */,
372 | );
373 | defaultConfigurationIsVisible = 0;
374 | defaultConfigurationName = Release;
375 | };
376 | /* End XCConfigurationList section */
377 |
378 | /* Begin XCSwiftPackageProductDependency section */
379 | AEA31F24286668E800779C07 /* TextFieldAlert */ = {
380 | isa = XCSwiftPackageProductDependency;
381 | productName = TextFieldAlert;
382 | };
383 | /* End XCSwiftPackageProductDependency section */
384 | };
385 | rootObject = AEA31F05286666B300779C07 /* Project object */;
386 | }
387 |
--------------------------------------------------------------------------------
/Example/TextFieldAlertExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/TextFieldAlertExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/TextFieldAlertExample/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 |
--------------------------------------------------------------------------------
/Example/TextFieldAlertExample/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/Example/TextFieldAlertExample/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/TextFieldAlertExample/ExampleView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import TextFieldAlert
3 |
4 | struct ExampleView: View {
5 | @StateObject var viewModel = ExampleViewModel()
6 |
7 | var body: some View {
8 | VStack {
9 | Spacer()
10 | Spacer()
11 |
12 | Button {
13 | viewModel.presentSignInAlert()
14 | } label: {
15 | Text("Sign in")
16 | }
17 |
18 | Spacer()
19 | Spacer()
20 |
21 | Text("Mail: \(viewModel.mail)")
22 | Text("Password: \(viewModel.password)")
23 |
24 | Spacer()
25 | }
26 | .textFieldAlert(
27 | title: "Sign in",
28 | message: "Enter the following information.",
29 | textFields: [
30 | .init(
31 | text: $viewModel.mail,
32 | placeholder: "Email address (cannot be empty)",
33 | autocapitalizationType: .none,
34 | keyboardType: .emailAddress
35 | ),
36 | .init(
37 | text: $viewModel.password,
38 | placeholder: "Password (5 characters or more)",
39 | isSecureTextEntry: true,
40 | autocapitalizationType: .none
41 | )
42 | ],
43 | actions: [
44 | .init(
45 | title: "Cancel",
46 | style: .cancel
47 | ),
48 | .init(
49 | title: "OK",
50 | isEnabled: $viewModel.isValid,
51 | closure: { _ in
52 | viewModel.signIn()
53 | }
54 | )
55 | ],
56 | isPresented: $viewModel.isPresented
57 | )
58 | }
59 | }
60 |
61 | struct ExampleViewPreviews: PreviewProvider {
62 | static var previews: some View {
63 | ExampleView()
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Example/TextFieldAlertExample/ExampleViewModel.swift:
--------------------------------------------------------------------------------
1 | import Combine
2 |
3 | final class ExampleViewModel: ObservableObject {
4 |
5 | @Published var isPresented = false
6 | @Published var mail = ""
7 | @Published var password = ""
8 | @Published var isValid = false
9 |
10 | private var cancellable: AnyCancellable?
11 |
12 | init() {
13 | cancellable = Publishers.CombineLatest($mail, $password)
14 | .sink { value in
15 | let isMailValid = !value.0.isEmpty
16 | let isPasswordValid = value.1.count >= 5
17 | self.isValid = isMailValid && isPasswordValid
18 | }
19 | }
20 |
21 | func presentSignInAlert() {
22 | mail.removeAll()
23 | password.removeAll()
24 |
25 | isPresented = true
26 | }
27 |
28 | func signIn() {
29 | print("Signing in with email address '\(mail)' and password '\(password)'…")
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Example/TextFieldAlertExample/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/TextFieldAlertExample/TextFieldAlertExampleApp.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | @main
4 | struct TextFieldAlertExampleApp: App {
5 | var body: some Scene {
6 | WindowGroup {
7 | ExampleView()
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2022-23 Piotr Sochalewski
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "TextFieldAlert",
7 | platforms: [
8 | .iOS(.v13),
9 | .tvOS(.v13)
10 | ],
11 | products: [
12 | .library(
13 | name: "TextFieldAlert",
14 | targets: ["TextFieldAlert"]
15 | )
16 | ],
17 | targets: [
18 | .target(
19 | name: "TextFieldAlert",
20 | dependencies: []
21 | )
22 | ]
23 | )
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TextFieldAlert
2 |
3 | A SwiftUI alert with text field(s) for iOS 13+ and tvOS 13+.
4 |
5 | As Apple is [going to introduce](https://sarunw.com/posts/swiftui-alert-textfield/) text field(s) as an alert actions in iOS 16, this is a good replacement for older versions.
6 |
7 | ## Requirements
8 | * iOS 13.0+ or tvOS 13.0+
9 | * Swift 5.1+
10 |
11 | ## Installation
12 |
13 | You can add TextFieldAlert to an Xcode project by adding it as a package dependency.
14 |
15 | 1. From the **File** menu, select **Add Packages…**
16 | 2. Enter `https://github.com/sochalewski/TextFieldAlert` into the package repository URL text field.
17 | 3. Add the package to your app target.
18 |
19 | ## Usage
20 |
21 | TextFieldAlert is exposed through `View` extension:
22 |
23 | ```swift
24 | func textFieldAlert(
25 | title: String?,
26 | message: String?,
27 | textFields: [TextFieldAlert.TextField],
28 | actions: [TextFieldAlert.Action],
29 | isPresented: Binding
30 | )
31 | ```
32 |
33 | You can use it in your code as follows:
34 |
35 | ```swift
36 | struct ExampleView: View {
37 |
38 | @State var isPresented = false
39 | @State var text1 = ""
40 | @State var text2 = ""
41 |
42 | var body: some View {
43 | VStack {
44 | Button {
45 | isPresented = true
46 | } label: {
47 | Text("Alert")
48 | }
49 |
50 | Text(text1)
51 | Text(text2)
52 | }
53 | .textFieldAlert(
54 | title: "Title",
55 | message: "Message",
56 | textFields: [
57 | .init(text: $text1),
58 | .init(text: $text2)
59 | ],
60 | actions: [
61 | .init(title: "OK")
62 | ],
63 | isPresented: $isPresented
64 | )
65 | }
66 | }
67 | ```
68 |
69 | More advanced usage (incl. moving the responsibility to a view model, some customization and enabling action buttons conditionally) is available in the Example app.
70 |
71 |
72 |
73 | ## Author
74 |
75 | Piotr Sochalewski, sochalewski.github.io
76 |
77 | TextFieldAlert is heavily inspired by [tanzolone](https://stackoverflow.com/users/3033314/tanzolone)'s [answer](https://stackoverflow.com/a/61902990) on StackOverflow.
78 |
79 | ## License
80 |
81 | TextFieldAlert is available under the MIT license. See the LICENSE file for more info.
--------------------------------------------------------------------------------
/Resources/TextFieldAlert.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sochalewski/TextFieldAlert/f10c8a27cde60e1714f85041501344b916bf557e/Resources/TextFieldAlert.gif
--------------------------------------------------------------------------------
/Sources/TextFieldAlert/Extensions.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import UIKit
3 | import Combine
4 |
5 | public extension View {
6 | /// Presents a text field alert when a given condition is true.
7 | /// - Parameters:
8 | /// - title: A text string used as the title of the alert.
9 | /// - message: A text string used as the message of the alert.
10 | /// - textFields: An array of models used as text fields of the alert.
11 | /// - actions: An array of models used as actions of the alert.
12 | /// - isPresented: A binding to a Boolean value that determines whether to present the alert. When the user presses or taps one of the alert's actions, the system sets this value to `false` and dismisses.
13 | /// - alwaysPreferUIKit: A Boolean value that determines whether to always prefer an `UIAlertController`-backed view over a native alert on iOS 17 and greater.
14 | @ViewBuilder func textFieldAlert(
15 | title: String?,
16 | message: String? = nil,
17 | textFields: [TextFieldAlert.TextField],
18 | actions: [TextFieldAlert.Action],
19 | isPresented: Binding,
20 | alwaysPreferUIKit: Bool = false
21 | ) -> some View {
22 | if #available(iOS 17, tvOS 100000, *), !alwaysPreferUIKit {
23 | alert(
24 | title ?? "",
25 | isPresented: isPresented,
26 | actions: {
27 | ForEach(textFields) { textField in
28 | if textField.isSecureTextEntry {
29 | SecureField(textField.placeholder ?? "", text: textField.text)
30 | } else {
31 | TextField(textField.placeholder ?? "", text: textField.text)
32 | .autocapitalization(textField.autocapitalizationType)
33 | .disableAutocorrection(textField.autocorrectionType == .no)
34 | .keyboardType(textField.keyboardType)
35 | }
36 | }
37 |
38 | ForEach(actions) { action in
39 | Button(
40 | action.title,
41 | role: action.style.role,
42 | action: { action.closure?(textFields.map { $0.text.wrappedValue }) }
43 | )
44 | .disabled(!action.isEnabled.wrappedValue)
45 | }
46 | },
47 | message: {
48 | message != nil ? Text(message!) : nil
49 | }
50 | )
51 | } else {
52 | TextFieldWrapper(
53 | isPresented: isPresented,
54 | presentingView: self,
55 | content: {
56 | TextFieldAlert(
57 | title: title,
58 | message: message,
59 | textFields: textFields,
60 | actions: actions
61 | )
62 | }
63 | )
64 | }
65 | }
66 | }
67 |
68 | extension UIAlertAction.Style {
69 | @available(iOS 15, tvOS 15, *)
70 | var role: ButtonRole? {
71 | switch self {
72 | case .cancel:
73 | return .cancel
74 | case .destructive:
75 | return .destructive
76 | case .default:
77 | fallthrough
78 | @unknown default:
79 | return nil
80 | }
81 | }
82 | }
83 |
84 | extension UITextField {
85 | var textPublisher: AnyPublisher {
86 | NotificationCenter.default
87 | .publisher(for: UITextField.textDidChangeNotification, object: self)
88 | .map { ($0.object as? UITextField)?.text ?? "" }
89 | .eraseToAnyPublisher()
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/Sources/TextFieldAlert/TextFieldAlert.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | /// A model that describes a control that displays an alert with text fields.
4 | public struct TextFieldAlert {
5 | /// A model that describes a control that displays an editable text interface.
6 | public final class TextField: Identifiable {
7 | let text: Binding
8 | let placeholder: String?
9 | let isSecureTextEntry: Bool
10 | let autocapitalizationType: UITextAutocapitalizationType
11 | let autocorrectionType: UITextAutocorrectionType
12 | let keyboardType: UIKeyboardType
13 |
14 | /// Creates a model that describes a control that displays an editable text interface.
15 | /// - Parameters:
16 | /// - text: The text to display and edit.
17 | /// - placeholder: The string that displays when there is no other text in the text field.
18 | /// - isSecureTextEntry: A Boolean value that indicates whether a text object disables copying, and in some cases, prevents recording/broadcasting and also hides the text.
19 | /// - autocapitalizationType: The autocapitalization style for the text object.
20 | /// - autocorrectionType: The autocorrection style for the text object.
21 | /// - keyboardType: The keyboard type for the text object.
22 | public init(
23 | text: Binding = .constant(""),
24 | placeholder: String? = nil,
25 | isSecureTextEntry: Bool = false,
26 | autocapitalizationType: UITextAutocapitalizationType = .sentences,
27 | autocorrectionType: UITextAutocorrectionType = .default,
28 | keyboardType: UIKeyboardType = .default
29 | ) {
30 | self.text = text
31 | self.placeholder = placeholder
32 | self.isSecureTextEntry = isSecureTextEntry
33 | self.autocapitalizationType = autocapitalizationType
34 | self.autocorrectionType = autocorrectionType
35 | self.keyboardType = keyboardType
36 | }
37 | }
38 |
39 | /// A model that describes an action that can be taken when the user taps a button in an alert.
40 | public final class Action: Identifiable {
41 | let title: String
42 | let style: UIAlertAction.Style
43 | let isEnabled: Binding
44 | let closure: (([String]) -> Void)?
45 |
46 | /// Creates a model that describes an action that can be taken when the user taps a button in an alert.
47 | /// - Parameters:
48 | /// - title: The text to use for the button title.
49 | /// - style: Additional styling information to apply to the button.
50 | /// - isEnabled: A binding to a Boolean value indicating whether the action is currently enabled.
51 | /// - closure: A block to execute when the user selects the action. This block has no return value and takes an array of typed texts as its only parameter.
52 | public init(
53 | title: String,
54 | style: UIAlertAction.Style = .default,
55 | isEnabled: Binding = .constant(true),
56 | closure: (([String]) -> Void)? = nil
57 | ) {
58 | self.title = title
59 | self.style = style
60 | self.isEnabled = isEnabled
61 | self.closure = closure
62 | }
63 | }
64 |
65 | let title: String?
66 | let message: String?
67 | let textFields: [TextField]
68 | let actions: [Action]
69 | let isPresented: Binding?
70 |
71 | /// Creates a model that describes a control that displays an alert with text fields.
72 | /// - Parameters:
73 | /// - title: The title of the alert. Use this string to get the user’s attention and communicate the reason for the alert.
74 | /// - message: Descriptive text that provides additional details about the reason for the alert.
75 | /// - textFields: The array of text fields displayed by the alert.
76 | /// - actions: The actions that the user can take in response to the alert or action sheet.
77 | public init(
78 | title: String?,
79 | message: String? = nil,
80 | textFields: [TextField] = [],
81 | actions: [Action] = []
82 | ) {
83 | self.title = title
84 | self.message = message
85 | self.textFields = textFields
86 | self.actions = actions
87 | self.isPresented = nil
88 | }
89 |
90 | init(
91 | title: String?,
92 | message: String? = nil,
93 | textFields: [TextField] = [],
94 | actions: [Action] = [],
95 | isPresented: Binding?
96 | ) {
97 | self.title = title
98 | self.message = message
99 | self.textFields = textFields
100 | self.actions = actions
101 | self.isPresented = isPresented
102 | }
103 |
104 | func dismissible(_ isPresented: Binding) -> TextFieldAlert {
105 | TextFieldAlert(
106 | title: title,
107 | message: message,
108 | textFields: textFields,
109 | actions: actions,
110 | isPresented: isPresented
111 | )
112 | }
113 | }
114 |
115 | extension TextFieldAlert: UIViewControllerRepresentable {
116 | public func makeUIViewController(context: UIViewControllerRepresentableContext) -> TextFieldAlertViewController {
117 | TextFieldAlertViewController(alert: self)
118 | }
119 |
120 | public func updateUIViewController(
121 | _ uiViewController: UIViewControllerType,
122 | context: UIViewControllerRepresentableContext
123 | ) {
124 | guard let alertController = uiViewController.presentedViewController as? UIAlertController else { return }
125 |
126 | alertController.actions.enumerated().forEach { offset, action in
127 | action.isEnabled = actions[offset].isEnabled.wrappedValue
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/Sources/TextFieldAlert/TextFieldAlertViewController.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import SwiftUI
3 | import Combine
4 |
5 | public final class TextFieldAlertViewController: UIViewController {
6 |
7 | private let alert: TextFieldAlert
8 | private var cancellables = Set()
9 |
10 | init(
11 | alert: TextFieldAlert
12 | ) {
13 | self.alert = alert
14 | super.init(nibName: nil, bundle: nil)
15 | }
16 |
17 | required init?(coder: NSCoder) {
18 | fatalError("init(coder:) has not been implemented")
19 | }
20 |
21 | override public func viewDidAppear(_ animated: Bool) {
22 | super.viewDidAppear(animated)
23 |
24 | presentAlertController()
25 | }
26 |
27 | private func presentAlertController() {
28 | let alertController = UIAlertController(
29 | title: alert.title,
30 | message: alert.message,
31 | preferredStyle: .alert
32 | )
33 |
34 | alert.textFields.forEach { textField in
35 | alertController.addTextField { [weak self] in
36 | guard let self = self else { return }
37 | $0.text = textField.text.wrappedValue
38 | $0.textPublisher.assign(to: \.text.wrappedValue, on: textField).store(in: &self.cancellables)
39 | $0.placeholder = textField.placeholder
40 | $0.isSecureTextEntry = textField.isSecureTextEntry
41 | $0.autocapitalizationType = textField.autocapitalizationType
42 | $0.autocorrectionType = textField.autocorrectionType
43 | $0.keyboardType = textField.keyboardType
44 | }
45 | }
46 |
47 | alert.actions.forEach { action in
48 | let alertAction = UIAlertAction(
49 | title: action.title,
50 | style: action.style,
51 | handler: { [weak self, weak alertController] _ in
52 | self?.alert.isPresented?.wrappedValue = false
53 | action.closure?(alertController?.textFields?.map { $0.text ?? "" } ?? [])
54 | }
55 | )
56 | alertAction.isEnabled = action.isEnabled.wrappedValue
57 | alertController.addAction(alertAction)
58 | }
59 |
60 | present(alertController, animated: true)
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Sources/TextFieldAlert/TextFieldWrapper.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct TextFieldWrapper: View {
4 |
5 | @Binding var isPresented: Bool
6 | let presentingView: PresentingView
7 | let content: () -> TextFieldAlert
8 |
9 | var body: some View {
10 | ZStack {
11 | if isPresented {
12 | content().dismissible($isPresented)
13 | }
14 |
15 | presentingView
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------